Преглед на файлове

Merge pull request #4 from tizot/master

Translation and optimisation
Denis Merigoux преди 9 години
родител
ревизия
f1dc39855d

+ 3 - 0
README.md

@@ -25,6 +25,9 @@ Then simply use the django command:
25 25
 
26 26
     python manage.py runserver 0.0.0.0:8000
27 27
 
28
+If you want to use the Django Debug Toolbar, follow the instructions from the [https://django-debug-toolbar.readthedocs.io/en/1.6/installation.html](official documentation).  
29
+You just have to edit your `settings.py` file.
30
+
28 31
 ### Production
29 32
 
30 33
 Install the packages needed to run an Apache server with `wsgi_mod` :

+ 44 - 50
counter/models.py

@@ -1,92 +1,86 @@
1
-from django.db import models
2 1
 from datetime import datetime
3
-from babel.dates import format_timedelta
2
+
4 3
 from django.contrib.auth.models import User
4
+from django.db import models
5
+from django.utils.translation import ugettext_lazy as _, get_language
5 6
 
6
-# Create your models here.
7
+import arrow
8
+from babel.dates import format_timedelta
7 9
 
8 10
 
9 11
 class Counter(models.Model):
10
-    name = models.CharField('nom', max_length=60)
11
-    email = models.EmailField('email', max_length=264,
12
-                              default='null@localhost')
13
-    trigramme = models.CharField('trigramme', max_length=3)
14
-    user = models.ForeignKey(User, blank=True, null=True,
15
-                             verbose_name='utilisateur associé')
16
-    email_notifications = models.BooleanField(
17
-        'notifications par email', default=False)
18
-    sort_by_score = models.BooleanField(
19
-        'trier par SeumScore™', default=True)
12
+    name = models.CharField(_('name'), max_length=60)
13
+    email = models.EmailField(_('email'), max_length=264, default='null@localhost')
14
+    trigramme = models.CharField(_('trigram'), max_length=3)
15
+    user = models.ForeignKey(User, blank=True, null=True, verbose_name=_('associated user'))
16
+    email_notifications = models.BooleanField(_('email notifications'), default=False)
17
+    sort_by_score = models.BooleanField(_('sort by SeumScore'), default=True)
20 18
 
21 19
     def __str__(self):
22
-        return '%s (%s)' % (self.trigramme, self.name)
20
+        return _('%(trigram)s (%(name)s)') % {'trigram': self.trigramme, 'name': self.name}
23 21
 
24 22
     class Meta:
25
-        verbose_name = 'compteur'
23
+        verbose_name = _('counter')
26 24
 
27 25
 
28 26
 class Reset(models.Model):
29
-    timestamp = models.DateTimeField('date et heure', auto_now_add=True)
30
-    reason = models.TextField('raison')
31
-    counter = models.ForeignKey('Counter', related_name='counter',
32
-                                verbose_name='victime')
33
-    who = models.ForeignKey('Counter', related_name='who',
34
-                            verbose_name='fouteur de seum',
35
-                            blank=True, null=True, default=None)
27
+    timestamp = models.DateTimeField(_('datetime'), auto_now_add=True)
28
+    reason = models.TextField(_('reason'))
29
+    counter = models.ForeignKey('Counter', related_name='resets', verbose_name=_('victim'))
30
+    who = models.ForeignKey('Counter', related_name='who', verbose_name=_('seum giver'), blank=True, null=True, default=None)
36 31
 
37 32
     def __str__(self):
38
-        if (self.who is None or
39
-                self.who.id == self.counter.id):
40
-            return '%s : %s (%s)' % (self.counter,
41
-                                     format_timedelta(
42
-                                         datetime.now() -
43
-                                         self.timestamp.replace(tzinfo=None),
44
-                                         locale='fr'), self.reason)
33
+        if self.who is None or self.who == self.counter:
34
+            return _('%(counter)s: %(datetime)s (%(reason)s)') % {
35
+                'counter': self.counter,
36
+                'datetime': arrow.Arrow.fromdatetime(self.timestamp).humanize(locale=(get_language() or 'en')), # dirty hack...
37
+                'reason': self.reason
38
+            }
45 39
         else:
46
-            return '%s à %s : %s (%s)' % (self.who, self.counter,
47
-                                          format_timedelta(
48
-                                              datetime.now() -
49
-                                              self.timestamp.replace(
50
-                                                  tzinfo=None),
51
-                                              locale='fr'), self.reason)
40
+            return '%(who)s to %(counter)s : %(datetime)s (%(reason)s)' % {
41
+                'who': self.who,
42
+                'counter': self.counter,
43
+                'datetime': arrow.Arrow.fromdatetime(self.timestamp).humanize(locale=(get_language() or 'en')),
44
+                'reason': self.reason
45
+            }
52 46
 
53 47
     class Meta:
54
-        verbose_name = 'remise à zéro'
55
-        verbose_name_plural = 'remises à zéro'
48
+        verbose_name = _('reset')
49
+        verbose_name_plural = _('resets')
56 50
 
57 51
 
58 52
 class Like(models.Model):
59
-    liker = models.ForeignKey('Counter', verbose_name='likeur')
60
-    reset = models.ForeignKey('Reset', verbose_name='seum')
61
-    timestamp = models.DateTimeField('date et heure', auto_now_add=True)
53
+    liker = models.ForeignKey('Counter', verbose_name=_('liker'), related_name='likes')
54
+    reset = models.ForeignKey('Reset', verbose_name=_('seum'), related_name='likes')
55
+    timestamp = models.DateTimeField(_('datetime'), auto_now_add=True)
62 56
 
63 57
     class Meta:
64
-        verbose_name = 'like'
65
-        verbose_name_plural = 'likes'
58
+        verbose_name = _('like')
59
+        verbose_name_plural = _('likes')
66 60
         unique_together = ('liker', 'reset')
67 61
 
68 62
     def __str__(self):
69
-        return '%s aime %s' % (self.liker, self.reset)
63
+        return _('%(liker)s likes %(reset)s') % {'liker': self.liker, 'reset': self.reset}
70 64
 
71 65
 
72 66
 class Keyword(models.Model):
73 67
     text = models.CharField('texte', max_length=128, unique=True)
74 68
 
75 69
     class Meta:
76
-        verbose_name = 'mot-clé'
77
-        verbose_name_plural = 'mots-clés'
70
+        verbose_name = _('keyword')
71
+        verbose_name_plural = _('keywords')
78 72
 
79 73
     def __str__(self):
80 74
         return '#%s' % (self.text)
81 75
 
82 76
 
83 77
 class Hashtag(models.Model):
84
-    keyword = models.ForeignKey('Keyword', verbose_name='hashtag')
85
-    reset = models.ForeignKey('Reset', verbose_name='remise à zéro')
78
+    keyword = models.ForeignKey('Keyword', verbose_name=_('hashtag'), related_name='hashtags')
79
+    reset = models.ForeignKey('Reset', verbose_name=_('reset'), related_name='hashtags')
86 80
 
87 81
     class Meta:
88
-        verbose_name = 'hashtag'
89
-        verbose_name_plural = 'hashtags'
82
+        verbose_name = _('hashtag')
83
+        verbose_name_plural = _('hashtags')
90 84
 
91 85
     def __str__(self):
92
-        return '%s pour %s' % (self.keyword, self.reset)
86
+        return _('%(keyword)s for %(who)s') % {'keyword': self.keyword, 'who': self.reset}

+ 19 - 0
counter/templates/baseTemplate.html

@@ -1,3 +1,7 @@
1
+{% load i18n %}
2
+
3
+{% get_current_language as LANGUAGE_CODE %}
4
+
1 5
 <!DOCTYPE html>
2 6
 <html>
3 7
 
@@ -71,6 +75,21 @@
71 75
 	</script>
72 76
 </head>
73 77
 <section id="content">
78
+	<div class="container-fluid" style="padding-top: 15px; ">
79
+ 		<div class="row">
80
+ 			<div class="col-md-12">
81
+ 				<form class="form-horizontal" action="{% url 'set_language' %}" method="post">{% csrf_token %}
82
+ 				{% if LANGUAGE_CODE == 'fr' %}
83
+ 					<input name="language" type="hidden" value="en" />
84
+					<input type="submit" class="btn btn-info pull-right" value="English version" />
85
+				{% else %}
86
+					<input name="language" type="hidden" value="fr" />
87
+					<input type="submit" class="btn btn-info pull-right" value="Version française" />
88
+ 				{% endif %}
89
+ 				</form>
90
+			</div>
91
+		</div>
92
+	</div>
74 93
 	{% block content %}{% endblock %}
75 94
 </section>
76 95
 </body>

+ 52 - 39
counter/templates/counterTemplate.html

@@ -1,7 +1,14 @@
1
-{% extends 'baseTemplate.html' %} {% block title %}{{counter.trigramme}}{% endblock %} {% block content %} {% load hashtags %}
1
+{% extends 'baseTemplate.html' %}
2
+
3
+{% block title %}{{counter.trigramme}}{% endblock %}
4
+
5
+{% block content %}
6
+{% load i18n %}
7
+{% load hashtags %}
8
+
2 9
 <div class="text-center">
3 10
   <h1>
4
-      <a class="counter-link" href="{% url 'home' %}"><b>{{counter.trigramme}}</b> <small>{{ counter.name }}</small></a>
11
+      <a class="counter-link" href="{% url 'home' %}"><b>{{ counter.trigramme }}</b> <small>{{ counter.name }}</small></a>
5 12
   </h1>
6 13
 </div>
7 14
 <div class="container-fluid">
@@ -19,16 +26,16 @@
19 26
                 </a>
20 27
                 {% if not counter.lastReset.noSeum %}
21 28
                 {% if counter.alreadyLiked %}
22
-                <span class="pull-right badge" {% if counter.likeCount > 0 %} data-toggle="tooltip" data-placement="top" title="{{ counter.likersString }}" {% endif %}>
23
-                    <span class="glyphicon glyphicon-ok"></span>&emsp;{{ counter.likeCount }}
29
+                <span class="pull-right badge" {% if counter.likes_count > 0 %} data-toggle="tooltip" data-placement="top" title="{{ counter.likersString }}" {% endif %}>
30
+                    <span class="glyphicon glyphicon-ok"></span>&emsp;{{ counter.likes_count }}
24 31
                 </span>
25 32
                 {% elif counter.id == myCounter.id or counter.lastReset.who.id == myCounter.id %}
26
-                <span class="pull-right badge" {% if counter.likeCount > 0 %} data-toggle="tooltip" data-placement="top" title="{{ counter.likersString }}" {% endif %}>
27
-                    <span class="glyphicon glyphicon-heart"></span>&emsp;{{ counter.likeCount }}
33
+                <span class="pull-right badge" {% if counter.likes_count > 0 %} data-toggle="tooltip" data-placement="top" title="{{ counter.likersString }}" {% endif %}>
34
+                    <span class="glyphicon glyphicon-heart"></span>&emsp;{{ counter.likes_count }}
28 35
                 </span>
29 36
                 {% else %}
30 37
                 <a class="pull-right badge" onclick="document.forms['like{{counter.id}}'].submit();">
31
-                    <span class="glyphicon glyphicon-heart"></span>&emsp;{{ counter.likeCount }}
38
+                    <span class="glyphicon glyphicon-heart"></span>&emsp;{{ counter.likes_count }}
32 39
                 </a>
33 40
                 {% endif %}
34 41
                 {% endif %}
@@ -37,32 +44,34 @@
37 44
         </div>
38 45
         <div class="seum-counter panel-body" style="height:125px" id="container{{counter.id}}">
39 46
           {% if counter.lastReset.noSeum %}
40
-          <strong>N'a pas encore eu le seum.</strong>
41
-          <br> {% else %}
42
-          <strong>
43
-          {% if counter.lastReset.selfSeum %}
44
-              A eu le seum il y a {{ counter.lastReset.formatted_delta }}.
47
+            <strong>{% trans "Has not got the seum yet" %}.</strong><br />
45 48
           {% else %}
46
-              {{ counter.lastReset.who.trigramme }} lui a foutu le seum il y a {{ counter.lastReset.formatted_delta }}.
49
+            <strong>
50
+            {% if counter.lastReset.selfSeum %}
51
+              {% trans "Has got the seum" %} {{ counter.lastReset.formatted_delta }}.
52
+            {% else %}
53
+              {{ counter.lastReset.who.trigramme }} {% trans "threw him/her the seum" %} {{ counter.lastReset.formatted_delta }}.
54
+            {% endif %}
55
+            </strong><br />
47 56
           {% endif %}
48
-          </strong>
49
-          <br> {% endif %}
50 57
 
51 58
           <p>{{ counter.lastReset.reason | hashtag }}</p>
52 59
           <div class="text-center">
53
-            <button id="button{{counter.id}}" class="btn btn-default btn-danger" type="button" onclick="revealSeumForm({{counter.id}})">Remettre à zéro</button>
60
+            <button id="button{{counter.id}}" class="btn btn-default btn-danger" type="button" onclick="revealSeumForm({{counter.id}})">
61
+              {% trans "Reset" %}
62
+            </button>
54 63
           </div>
55 64
           <form style="display:none" id="counter{{counter.id}}" action="{% url 'reset-counter' %}" method="post">
56 65
             {% csrf_token %}
57 66
             <div class="form-group">
58
-              <label for="reason">Motif du seum :</label>
67
+              <label for="reason">{% trans "Motive for the seum:" %}</label>
59 68
               <input id="reason{{counter.id}}" type="text" class="form-control" name="reason"></input>
60 69
             </div>
61 70
             <input type="hidden" name="counter" value="{{counter.id}}"></input>
62 71
             <input type="hidden" name="redirect" value="{% url 'counter' id_counter=counter.id %}"></input>
63 72
             <input type="hidden" name="who" value="{{myCounter.id}}"></input>
64 73
             <div class="text-center">
65
-              <button type="submit" class="btn btn-default btn-success">Foutre le seum</button>
74
+              <button type="submit" class="btn btn-default btn-success">{% trans "Throw the seum" %}</button>
66 75
             </div>
67 76
           </form>
68 77
         </div>
@@ -71,15 +80,15 @@
71 80
     <div class="col-md-9">
72 81
       <div class="panel panel-info">
73 82
         <div class="panel-heading">
74
-          <h2 class="panel-title">Timeline du seum</h2>
83
+          <h2 class="panel-title">{% trans "Timeline of the seum" %}</h2>
75 84
         </div>
76 85
         <div class="graphs timeline panel-body">
77 86
             {% if counter.lastReset.noSeum %}
78
-            <div class="text-center text-muted">
79
-                <p>Pas encore de timeline du seum...</p>
80
-            </div>
87
+              <div class="text-center text-muted">
88
+                <p>{% trans "No timeline of the seum yet..." %}</p>
89
+              </div>
81 90
             {% else %}
82
-            {{chart.as_html}}
91
+              {{ chart.as_html }}
83 92
             {% endif %}
84 93
         </div>
85 94
       </div>
@@ -89,30 +98,33 @@
89 98
     <div class="col-sm-12">
90 99
       <div class="panel panel-default">
91 100
         <div class="panel-heading">
92
-          <h2 class="panel-title">Historique du seum <small class="badge pull-right">{{seumFrequency}}/seum</small></h2>
101
+          <h2 class="panel-title">
102
+            {% trans "Seum history" %}
103
+            <small class="badge pull-right">{{ seumFrequency }}/seum</small>
104
+          </h2>
93 105
         </div>
94 106
         <div class="panel-body">
95 107
           <table class="table table-striped">
96 108
             <thead>
97 109
               <tr>
98
-                <th>Date</th>
99
-                <th>Motif</th>
100
-                <th>Fouteur de seum</th>
101
-                <th>Nombre de likes</th>
110
+                <th>{% trans "Date" %}</th>
111
+                <th>{% trans "Motive" %}</th>
112
+                <th>{% trans "Seum thrower" %}</th>
113
+                <th>{% trans "Number of likes" %}</th>
102 114
               </tr>
103 115
             </thead>
104 116
             <tbody>
105 117
               {% for reset in resets %}
106
-              <tr>
107
-                <td><b>{{ reset.date }}</b></td>
108
-                <td>{{ reset.reason | hashtag }}</td>
109
-                <td>
110
-                {% if not reset.selfSeum %}
111
-                    {{ reset.who.trigramme }}
112
-                {% endif %}
113
-                </td>
114
-                <td>{{ reset.likeCount }}</td>
115
-              </tr>
118
+                <tr>
119
+                  <td><b>{{ reset.date | date:"SHORT_DATETIME_FORMAT" }}</b></td>
120
+                  <td>{{ reset.reason | hashtag }}</td>
121
+                  <td>
122
+                    {% if not reset.selfSeum %}
123
+                      {{ reset.who.trigramme }}
124
+                    {% endif %}
125
+                  </td>
126
+                  <td>{{ reset.likes_count }}</td>
127
+                </tr>
116 128
               {% endfor %}
117 129
             </tbody>
118 130
           </table>
@@ -123,8 +135,9 @@
123 135
 </div>
124 136
 <div class="row">
125 137
   <div class="text-center">
126
-    <a class="btn btn-success" href="{% url 'home' %}">Retour à la liste des compteurs</a>
138
+    <a class="btn btn-success" href="{% url 'home' %}">{% trans "Back to counters list" %}</a>
127 139
   </div>
128 140
 </div>
129 141
 </div>
142
+
130 143
 {% endblock %}

+ 21 - 15
counter/templates/createUser.html

@@ -1,4 +1,10 @@
1
-{% extends 'baseTemplate.html' %} {% block title %}Changement du mot de passe{% endblock %}{% block content %}
1
+{% extends 'baseTemplate.html' %}
2
+
3
+{% block title %}Changement du mot de passe{% endblock %}
4
+
5
+{% block content %}
6
+{% load i18n %}
7
+
2 8
 <div class="container">
3 9
     <div class="row">
4 10
         <div class="text-center">
@@ -8,44 +14,44 @@
8 14
     <div class="row">
9 15
         <div class="panel panel-primary">
10 16
             <div class="panel-heading">
11
-                <h2 class="panel-title">Créé ton compteur de seum !</h2>
17
+                <h2 class="panel-title">{% trans "Create your seum counter!" %}</h2>
12 18
             </div>
13 19
             <div class=" panel-body">
14 20
                 <form method="POST">
15 21
                     {% csrf_token %}
16
-                    <p>L'adresse e-mail sera utilisée pour la réinitialisation de ton mot de passe. Ton login sur le site sera la première partie de cette adresse (avant le @).</p>
22
+                    <p>{% trans "The email address will be used for password reinitialisation. Your login on this website will be the first part of this address (before the @)." %}</p>
17 23
                     <div class="form-group">
18
-                        <label for="id_email">Adresse email</label>
24
+                        <label for="id_email">{% trans "Email address" %}</label>
19 25
                         <input id="id_email" type="email" class="form-control" name="email" required />
20 26
                     </div>
21
-                    <p>Si tu coches la case en dessous, tu recevras un mail de <tt>seum@merigoux.ovh</tt> à chaque fois que quelqu'un aura le seum sur le site. Spammatoire mais jouissif, peut-être désactivé ou réactivé par la suite.</p>
27
+                    <p>{% trans "If you check the box below, you will receive an email from <tt>seum@merigoux.ovh</tt> each time someone get the seum on the site. Spamming but so enjoyable, can be deactivated and reactivated later." %}
22 28
                     <div class="form-check">
23 29
                         <input type="checkbox" class="form-check-input" name="email_notifications">
24 30
                         <label class="form-check-label">
25
-                            Notifications par email
31
+                            {% trans "Email notifications" %}
26 32
                         </label>
27 33
                     </div>
28
-                    <p>Les autres utilisateurs ne pourront voir que ton pseudo et ton trigramme, ce sera ton identité seumesque !</p>
34
+                    <p>{% trans "Other users will see your nickname and your trigram only, it will be your seum identity!" %}</p>
29 35
                     <div class="form-group">
30
-                        <label for="id_trigramme">Trigramme</label>
36
+                        <label for="id_trigramme">{% trans "Trigram" %}</label>
31 37
                         <input id="id_trigramme" maxlength="3" type="text" class="form-control text-uppercase" name="trigramme" onkeyup="this.value=this.value.toUpperCase();" required />
32 38
                     </div>
33 39
                     <div class="form-group">
34
-                        <label for="id_nick">Pseudo</label>
40
+                        <label for="id_nick">{% trans "Nick" %}</label>
35 41
                         <input id="id_nick" type="text" class="form-control" name="nick" required />
36 42
                     </div>
37
-                    <p>J'aurais pu exiger 10 caractères dont un chiffre, une lettre et un tilde avec 3 majuscules et 2 minuscules pour te foutre le seum mais en fait tu peux mettre ce que tu veux.</p>
43
+                    <p>{% trans "I could have required 10 characters with one digit, an emoji, three uppercase letters and two lowercase ones to throw you the seum, but actually you can choose whatever you want." %}</p>
38 44
                     <div class="form-group">
39
-                        <label for="id_password1">Mot de passe</label>
45
+                        <label for="id_password1">{% trans "Password" %}</label>
40 46
                         <input id="id_password1" type="password" class="form-control" name="password1" required />
41 47
                     </div>
42 48
                     <div class="form-group">
43
-                        <label for="id_password2">Confirmer le mot de passe</label>
49
+                        <label for="id_password2">{% trans "Confirm password" %}</label>
44 50
                         <input id="id_password2" type="password" class="form-control" name="password2" required />
45 51
                     </div>
46
-                    <p>Si ce formulaire t'as foutu le seum, n'oublie pas de remettre ton compteur à zéro en arrivant sur le site.</p>
52
+                    <p>{% trans "If this form has given you the seum, do not forget to reset your counter once you are logged in!" %}</p>
47 53
                     <div class="text-center">
48
-                        <button type="submit" class="btn btn-default btn-success">Créer le compteur</button>
54
+                        <button type="submit" class="btn btn-default btn-success">{% trans "Create the counter" %}</button>
49 55
                     </div>
50 56
                 </form>
51 57
             </div>
@@ -55,7 +61,7 @@
55 61
     <div class="row">
56 62
         <div class="panel panel-danger">
57 63
             <div class="panel-heading">
58
-                <h2 class="panel-title">Erreur</h2>
64
+                <h2 class="panel-title">{% trans "Error" %}</h2>
59 65
             </div>
60 66
             <div class=" panel-body">
61 67
                 <p>{{error}}</p>

+ 10 - 4
counter/templates/createUserDone.html

@@ -1,4 +1,10 @@
1
-{% extends 'baseTemplate.html' %} {% block title %}Mot de passe changé !{% endblock %}{% block content %}
1
+{% extends 'baseTemplate.html' %}
2
+
3
+{% load i81n %}
4
+
5
+{% block title %}{% trans "Password successfully changed!" %}{% endblock %}
6
+
7
+{% block content %}
2 8
 <div class="container">
3 9
     <div class="row">
4 10
         <div class="text-center">
@@ -8,12 +14,12 @@
8 14
     <div class="row">
9 15
         <div class="panel panel-primary">
10 16
             <div class="panel-heading">
11
-                <h2 class="panel-title">Victoire !</h2>
17
+                <h2 class="panel-title">{% trans "Victory!" %}</h2>
12 18
             </div>
13 19
             <div class=" panel-body">
14
-                <p>Tu as créé ton compteur ! Ton login est <b>{{login}}</b>.</p>
20
+                <p>{% trans "You have created your counter! Your login is " %} <b>{{ login }}</p>.</p>
15 21
                 <div class="text-center">
16
-                    <a href="{% url 'login' %}" type="submit" class="btn btn-default btn-success">Connecte-toi pour accéder au site !</a>
22
+                    <a href="{% url 'login' %}" type="submit" class="btn btn-default btn-success">{% trans "Login to access the website!" %}</a>
17 23
                 </div>
18 24
             </div>
19 25
         </div>

+ 18 - 10
counter/templates/hashtagTemplate.html

@@ -1,7 +1,14 @@
1
-{% extends 'baseTemplate.html' %} {% block title %}{{hashtag}}{% endblock %} {% block content %} {% load hashtags %}
1
+{% extends 'baseTemplate.html' %}
2
+
3
+{% block title %}{{hashtag}}{% endblock %}
4
+
5
+{% block content %}
6
+{% load i18n %}
7
+{% load hashtags %}
8
+
2 9
 <div class="text-center">
3 10
   <h1>
4
-      <a class="counter-link" href="{% url 'home' %}"><b>{{hashtag}}</b></a>
11
+    <a class="counter-link" href="{% url 'home' %}"><b>{{hashtag}}</b></a>
5 12
   </h1>
6 13
 </div>
7 14
 <div class="container-fluid">
@@ -9,23 +16,24 @@
9 16
     <div class="col-sm-12">
10 17
       <div class="panel panel-default">
11 18
         <div class="panel-heading">
12
-          <h2 class="panel-title">Liste des seums contenant {{ hashtag }}<small class="badge pull-right">{{ totalNumber }}</small></h2>
19
+          <h2 class="panel-title">
20
+            {% trans "Seums containing" %} {{ hashtag }}<small class="badge pull-right">{{ totalNumber }}</small></h2>
13 21
         </div>
14 22
         <div class="panel-body">
15 23
           <table class="table table-striped">
16 24
             <thead>
17 25
               <tr>
18
-                <th>Date</th>
19
-                <th>Motif</th>
20
-                <th>Victime</th>
21
-                <th>Fouteur de seum</th>
22
-                <th>Nombre de likes</th>
26
+                <th>{% trans "Date" %}</th>
27
+                <th>{% trans "Motive" %}</th>
28
+                <th>{% trans "Victim" %}</th>
29
+                <th>{% trans "Seum thrower" %}</th>
30
+                <th>{% trans "Number of likes" %}</th>
23 31
               </tr>
24 32
             </thead>
25 33
             <tbody>
26 34
               {% for reset in resets %}
27 35
               <tr>
28
-                <td><b>{{ reset.date }}</b></td>
36
+                <td><b>{{ reset.timestamp | date:"SHORT_DATETIME_FORMAT" }}</b></td>
29 37
                 <td>{{ reset.reason | hashtag }}</td>
30 38
                 <td>{{ reset.counter.trigramme }}</td>
31 39
                 <td>
@@ -45,7 +53,7 @@
45 53
 </div>
46 54
 <div class="row">
47 55
   <div class="text-center">
48
-    <a class="btn btn-success" href="{% url 'home' %}">Retour à la liste des compteurs</a>
56
+    <a class="btn btn-success" href="{% url 'home' %}">{% trans "Back to counters list" %}</a>
49 57
   </div>
50 58
 </div>
51 59
 </div>

+ 99 - 74
counter/templates/homeTemplate.html

@@ -1,9 +1,17 @@
1
-{% extends 'baseTemplate.html' %} {% block title %}Compteurs{% endblock %} {% block content %} {% load hashtags %}
1
+{% extends 'baseTemplate.html' %}
2
+
3
+{% load i18n %}
4
+{% load hashtags %}
5
+
6
+{% block title %}{% trans "Counters" %}{% endblock %}
7
+
8
+{% block content %}
2 9
 <div class="text-center">
3 10
 	<h1><a class="counter-link" href="{% url 'home' %}">SeumBook™</a></h1>
4 11
 </div>
5 12
 <div class="container-fluid">
6 13
 	<div class="row" id="my-counter">
14
+		{# my counter #}
7 15
 		<div class="col-sm-6">
8 16
 			<div class="panel panel-primary">
9 17
 				<div class="panel-heading">
@@ -21,60 +29,64 @@
21 29
 				<div class="primary-counter panel-body" id="container{{myCounter.id}}">
22 30
 					<div style="width:100%;">
23 31
 						{% if myCounter.lastReset.noSeum %}
24
-						<strong>N'a pas encore eu le seum.</strong>
25
-						<br> {% else %}
26
-						<strong>
27
-						{% if myCounter.lastReset.selfSeum %}
28
-						J'ai eu le seum il y a {{ myCounter.lastReset.formatted_delta }}.
32
+							<strong>{% trans "Has not got the seum yet" %}.</strong><br />
29 33
 						{% else %}
30
-						{{myCounter.lastReset.who.trigramme}} m'a foutu le seum il y a {{ myCounter.lastReset.formatted_delta }}.
34
+							<strong>
35
+								{% if myCounter.lastReset.selfSeum %}
36
+									{% trans "I got the seum" %} {{ myCounter.lastReset.formatted_delta }}.
37
+								{% else %}
38
+									{{ myCounter.lastReset.who.trigramme }} {% trans "threw me the seum"} {{ myCounter.lastReset.formatted_delta }}.
39
+								{% endif %}
40
+							</strong><br />
31 41
 						{% endif %}
32
-					</strong>
33
-						<br> {% endif %}
34 42
 						<p>{{ myCounter.lastReset.reason | hashtag }}</p>
35 43
 						<div class="text-center" id="button{{myCounter.id}}">
36
-							<button class="btn btn-default btn-danger" type="button" onclick="revealSeumForm({{myCounter.id}})">Remettre à zéro</button>
44
+							<button class="btn btn-default btn-danger" type="button" onclick="revealSeumForm({{myCounter.id}})">
45
+								{% trans "Reset" %}
46
+							</button>
37 47
 						</div>
48
+						{# reset form for my counter #}
38 49
 						<form style="display:none" id="counter{{myCounter.id}}" action="{% url 'reset-counter' %}" method="post">
39 50
 							{% csrf_token %}
40 51
 							<div class="form-group">
41
-								<label for="reason">Motif du seum</label>
52
+								<label for="reason">{% trans "Motive of the seum" %}</label>
42 53
 								<input id="reason{{myCounter.id}}" type="text" class="form-control" name="reason"></input>
43 54
 							</div>
44 55
 							<input type="hidden" name="counter" value="{{myCounter.id}}"></input>
45 56
 							<input type="hidden" name="redirect" value="{% url 'home' %}"></input>
46 57
 							<input type="hidden" name="who" value="{{myCounter.id}}"></input>
47 58
 							<div class="text-center">
48
-								<button type="submit" class="btn btn-default btn-success">J'ai le seum</button>
59
+								<button type="submit" class="btn btn-default btn-success">{% trans "I've got the seum" %}</button>
49 60
 							</div>
50 61
 						</form>
51 62
 					</div>
52 63
 				</div>
53 64
 			</div>
54 65
 		</div>
66
+		{# QuickSeum #}
55 67
 		<div class="col-sm-6">
56 68
 			<div class="panel panel-primary">
57 69
 				<div class="panel-heading">
58
-					<h2 class="panel-title"><b>QuickSeum™</b> <small>Brise le mur du seum</small></h2>
70
+					<h2 class="panel-title"><b>QuickSeum™</b> <small>{% trans "Break the seum wall" %}</small></h2>
59 71
 				</div>
60 72
 				<div class="primary-counter panel-body">
61 73
 					<form class="form-horizontal" action="{% url 'reset-counter'%}" method="POST" style="width:100%;">
62 74
 						{% csrf_token %}
63 75
 						<div class="form-group">
64
-							<label for="id_quicktrigramme" class="col-sm-3 control-label">Trigramme</label>
76
+							<label for="id_quicktrigramme" class="col-sm-3 control-label">{% trans "Trigram" %}</label>
65 77
 							<div class="col-sm-9">
66 78
 								<input id="id_quicktrigramme" maxlength="3" type="text" class="form-control text-uppercase" name="trigramme" onkeyup="this.value=this.value.toUpperCase();" required />
67 79
 							</div>
68 80
 						</div>
69 81
 						<div class="form-group">
70
-							<label for="id_quickreason" class="col-sm-3 control-label">Motif</label>
82
+							<label for="id_quickreason" class="col-sm-3 control-label">{% trans "Motive" %}</label>
71 83
 							<div class="col-sm-9">
72 84
 								<input type="text" class="form-control" id="id_quickreason" name="reason" />
73 85
 							</div>
74 86
 						</div>
75 87
 						<div class="form-group">
76 88
 							<div class="col-sm-offset-3 col-sm-9">
77
-								<button type="submit" class="btn btn-danger">Foutre le seum</button>
89
+								<button type="submit" class="btn btn-danger">{% trans "Throw the seum" %}</button>
78 90
 							</div>
79 91
 						</div>
80 92
 						<input type="hidden" name="who" value="{{myCounter.id}}"></input>
@@ -84,6 +96,7 @@
84 96
 			</div>
85 97
 		</div>
86 98
 	</div>
99
+	{# Counters panel #}
87 100
 	<div class="row">
88 101
 		{% for counter in counters %}
89 102
 		<div class="col-md-4 col-sm-6 col-lg-3">
@@ -99,54 +112,56 @@
99 112
 								<b>{{ counter.trigramme }}</b> <small>{{ counter.name }}</small>
100 113
 							</a>
101 114
 							{% if not counter.lastReset.noSeum %}
102
-							{% if counter.alreadyLiked %}
103
-							<span class="pull-right badge" {% if counter.likeCount > 0 %} data-toggle="tooltip" data-placement="top" title="{{ counter.likersString }}" {% endif %}>
104
-								<span class="glyphicon glyphicon-ok"></span>&emsp;{{ counter.likeCount }}
105
-							</span>
106
-							{% elif counter.id == myCounter.id or counter.lastReset.who.id == myCounter.id %}
107
-			                <span class="pull-right badge" {% if counter.likeCount > 0 %} data-toggle="tooltip" data-placement="top" title="{{ counter.likersString }}" {% endif %}>
108
-			                    <span class="glyphicon glyphicon-heart"></span>&emsp;{{ counter.likeCount }}
109
-			                </span>
110
-							{% else %}
111
-							<a class="pull-right badge" onclick="document.forms['like{{counter.id}}'].submit();">
112
-								<span class="glyphicon glyphicon-heart"></span>&emsp;{{ counter.likeCount }}
113
-							</a>
114
-							{% endif %}
115
+								{% if counter.alreadyLiked %}
116
+									<span class="pull-right badge" {% if counter.likeCount > 0 %} data-toggle="tooltip" data-placement="top" title="{{ counter.likersString }}" {% endif %}>
117
+										<span class="glyphicon glyphicon-ok"></span>&emsp;{{ counter.likeCount }}
118
+									</span>
119
+								{% elif counter.id == myCounter.id or counter.lastReset.who.id == myCounter.id %}
120
+			                		<span class="pull-right badge" {% if counter.likeCount > 0 %} data-toggle="tooltip" data-placement="top" title="{{ counter.likersString }}" {% endif %}>
121
+			                    		<span class="glyphicon glyphicon-heart"></span>&emsp;{{ counter.likeCount }}
122
+			                		</span>
123
+								{% else %}
124
+									<a class="pull-right badge" onclick="document.forms['like{{counter.id}}'].submit();">
125
+										<span class="glyphicon glyphicon-heart"></span>&emsp;{{ counter.likeCount }}
126
+									</a>
127
+								{% endif %}
115 128
 							{% endif %}
116 129
 						</h2>
117 130
 					</form>
118 131
 				</div>
119 132
 				<div class="seum-counter panel-body">
120 133
 					{% if counter.lastReset.noSeum %}
121
-					<strong>N'a pas encore eu le seum.</strong>
122
-					<br> {% else %}
123
-					<strong>
124
-					{% if counter.lastReset.selfSeum %}
125
-						A eu le seum il y a {{ counter.lastReset.formatted_delta }}.
134
+						<strong>{% trans "Has not got the seum yet" %}</strong><br />
126 135
 					{% else %}
127
-						{{ counter.lastReset.who.trigramme }} lui a foutu le seum il y a {{ counter.lastReset.formatted_delta }}.
136
+						<strong>
137
+							{% if counter.lastReset.selfSeum %}
138
+								{% trans "Has got the seum" %} {{ counter.lastReset.formatted_delta }}.
139
+							{% else %}
140
+								{{ counter.lastReset.who.trigramme }} {% trans "threw him/her the seum" %} {{ counter.lastReset.formatted_delta }}.
141
+							{% endif %}
142
+						</strong><br />
128 143
 					{% endif %}
129
-					</strong>
130
-					<br> {% endif %}
131
-
132 144
 					<p>{{ counter.lastReset.reason | hashtag }}</p>
133 145
 				</div>
134 146
 			</div>
135 147
 		</div>
136 148
 		{% endfor %}
137 149
 	</div>
150
+	{# Graphs #}
138 151
 	<div class="row">
139 152
 		<div class="col-sm-12">
140 153
 			<div class="panel panel-info">
141 154
 				<div class="panel-heading">
142
-					<h2 class="panel-title">Timeline des 24 heures du seum</h2>
155
+					<h2 class="panel-title">{% trans "Timeline of the 24h of the seum" %}{# Timeline des 24 heures du seum #}</h2>
143 156
 				</div>
144 157
 				<div class="panel-body timeline graphs">
145 158
 					{% if noTimeline %}
146
-					<div class="text-center text-muted">
147
-						<p>Pas de seum dans les dernières 24 heures...</p>
148
-					</div>
149
-					{% else %} {{ line_chart.as_html }} {% endif %}
159
+						<div class="text-center text-muted">
160
+							<p>{% trans "No seum in the last past 24h..." %}</p>
161
+						</div>
162
+					{% else %}
163
+						{{ line_chart.as_html }}
164
+					{% endif %}
150 165
 				</div>
151 166
 			</div>
152 167
 		</div>
@@ -155,14 +170,16 @@
155 170
 		<div class="col-sm-12">
156 171
 			<div class="panel panel-info">
157 172
 				<div class="panel-heading">
158
-					<h2 class="panel-title">Meilleurs seumeurs</h2>
173
+					<h2 class="panel-title">{% trans "Best seumers" %}</h2>
159 174
 				</div>
160 175
 				<div class="panel-body graphs">
161 176
 					{% if noBestSeum %}
162
-					<div class="text-center text-muted">
163
-						<p>Personne n'a eu le seum...</p>
164
-					</div>
165
-					{% else %} {{ best_chart.as_html }} {% endif %}
177
+						<div class="text-center text-muted">
178
+							<p>{% trans "Nobody has got the seum..." %}</p>
179
+						</div>
180
+					{% else %}
181
+						{{ best_chart.as_html }}
182
+					{% endif %}
166 183
 				</div>
167 184
 			</div>
168 185
 		</div>
@@ -171,14 +188,16 @@
171 188
 		<div class="col-sm-12">
172 189
 			<div class="panel panel-info">
173 190
 				<div class="panel-heading">
174
-					<h2 class="panel-title">Seumeurs les plus likés</h2>
191
+					<h2 class="panel-title">{% trans "Most liked seumers" %}</h2>
175 192
 				</div>
176 193
 				<div class="panel-body graphs">
177 194
 					{% if noBestLikees %}
178
-					<div class="text-center text-muted">
179
-						<p>Personne n'a liké...</p>
180
-					</div>
181
-					{% else %} {{ likees_chart.as_html }} {% endif %}
195
+						<div class="text-center text-muted">
196
+							<p>{% trans "Nobody liked..." %}</p>
197
+						</div>
198
+					{% else %}
199
+						{{ likees_chart.as_html }}
200
+					{% endif %}
182 201
 				</div>
183 202
 			</div>
184 203
 		</div>
@@ -187,14 +206,16 @@
187 206
 		<div class="col-sm-12">
188 207
 			<div class="panel panel-info">
189 208
 				<div class="panel-heading">
190
-					<h2 class="panel-title">Hashtags les plus populaires</h2>
209
+					<h2 class="panel-title">{% trans "Most popular hashtags" %}</h2>
191 210
 				</div>
192 211
 				<div class="panel-body graphs">
193 212
 					{% if noBestHashtags %}
194
-					<div class="text-center text-muted">
195
-						<p>Personne n'a utilisé de hashtags...</p>
196
-					</div>
197
-					{% else %} {{ hashtags_chart.as_html }} {% endif %}
213
+						<div class="text-center text-muted">
214
+							<p>{% trans "Nobody used any hashtag..." %}</p>
215
+						</div>
216
+					{% else %}
217
+						{{ hashtags_chart.as_html }}
218
+					{% endif %}
198 219
 				</div>
199 220
 			</div>
200 221
 		</div>
@@ -203,14 +224,16 @@
203 224
 		<div class="col-sm-12">
204 225
 			<div class="panel panel-info">
205 226
 				<div class="panel-heading">
206
-					<h2 class="panel-title">Meilleurs likeurs de seum</h2>
227
+					<h2 class="panel-title">{% trans "Best likers of seum" %}</h2>
207 228
 				</div>
208 229
 				<div class="panel-body graphs">
209 230
 					{% if noBestLikers %}
210
-					<div class="text-center text-muted">
211
-						<p>Personne n'a liké...</p>
212
-					</div>
213
-					{% else %} {{ likers_chart.as_html }} {% endif %}
231
+						<div class="text-center text-muted">
232
+							<p>{% trans "Nobody liked..." %}</p>
233
+						</div>
234
+					{% else %}
235
+						{{ likers_chart.as_html }}
236
+					{% endif %}
214 237
 				</div>
215 238
 			</div>
216 239
 		</div>
@@ -219,34 +242,36 @@
219 242
 		<div class="col-sm-12">
220 243
 			<div class="panel panel-info">
221 244
 				<div class="panel-heading">
222
-					<h2 class="panel-title">Activité seumesque</h2>
245
+					<h2 class="panel-title">{% trans "Seum activity" %}</h2>
223 246
 				</div>
224 247
 				<div class="panel-body graphs">
225 248
 					{% if noSeumActivity %}
226
-					<div class="text-center text-muted">
227
-						<p>Personne n'a eu le seum...</p>
228
-					</div>
229
-					{% else %} {{ activity_chart.as_html }} {% endif %}
249
+						<div class="text-center text-muted">
250
+							<p>{% trans "Nobody has got the seum..." %}</p>
251
+						</div>
252
+					{% else %}
253
+						{{ activity_chart.as_html }}
254
+					{% endif %}
230 255
 				</div>
231 256
 			</div>
232 257
 		</div>
233 258
 	</div>
234 259
 </div>
235 260
 <div class="row text-center">
236
-	<a href="{% url 'logout' %}" class="btn btn-danger">Se déconnecter</a>
237
-	<a href="{% url 'password_change' %}" class="btn btn-warning">Changer de mot de passe</a>
261
+	<a href="{% url 'logout' %}" class="btn btn-danger">{% trans "Logout" %}</a>
262
+	<a href="{% url 'password_change' %}" class="btn btn-warning">{% trans "Change password" %}</a>
238 263
 	<a href="{% url 'toggle_email_notifications' %}" class="btn btn-info">
239 264
 		{% if myCounter.email_notifications %}
240
-		Désactiver les notifications par mail
265
+			{% trans "Deactivate email notifications" %}
241 266
 		{% else %}
242
-		Activer les notifications par mail
267
+			{% trans "Activate email notifications" %}
243 268
 		{% endif %}
244 269
 	</a>
245 270
 	<a href="{% url 'toggle_sort_score' %}" class="btn btn-success">
246 271
 		{% if myCounter.sort_by_score %}
247
-		Trier les seums par ancienneté
272
+			{% trans "Sort seums by date" %}
248 273
 		{% else %}
249
-		Trier les seums par score
274
+			{% trans "Sort seums by score" %}
250 275
 		{% endif %}
251 276
 	</a>
252 277
 </div>

+ 17 - 11
counter/templates/login.html

@@ -1,4 +1,10 @@
1
-{% extends 'baseTemplate.html' %} {% block title %}Login{% endblock %}{% block content %}
1
+{% extends 'baseTemplate.html' %}
2
+
3
+{% load i18n %}
4
+
5
+{% block title %}{% trans "Login" %}{% endblock %}
6
+
7
+{% block content %}
2 8
 <div class="container">
3 9
     <div class="row">
4 10
         <div class="text-center">
@@ -8,23 +14,23 @@
8 14
     <div class="row">
9 15
         <div class="panel panel-primary">
10 16
             <div class="panel-heading">
11
-                <h2 class="panel-title">Connecte toi pour avoir le seum !</h2>
17
+                <h2 class="panel-title">{% trans "Login to get the seum!" %}</h2>
12 18
             </div>
13 19
             <div class=" panel-body">
14 20
                 <form class='' method="POST" action="{% url 'login' %}">
15 21
                     {% csrf_token %}
16 22
                     <div class="form-group">
17
-                        <label for="id_username">Nom d'utilisateur</label>
23
+                        <label for="id_username">{% trans "Username" %}</label>
18 24
                         <input id="id_username" type="text" class="form-control" name="username" required />
19 25
                     </div>
20 26
                     <div class="form-group">
21
-                        <label for="id_username">Mot de passe</label>
27
+                        <label for="id_username">{% trans "Password" %}</label>
22 28
                         <input id="id_username" type="password" class="form-control" name="password" required />
23 29
                     </div>
24 30
                     <input type="hidden" name="next" value="{{ next }}" />
25 31
                     <div class="text-center">
26
-                        <button type="submit" class="btn btn-default btn-success">Se connecter</button>
27
-                        <a href="{% url 'password_reset' %}" class="btn btn-default btn-danger">Mot de passe oublié</a>
32
+                        <button type="submit" class="btn btn-default btn-success">{% trans "Log in" %}</button>
33
+                        <a href="{% url 'password_reset' %}" class="btn btn-default btn-danger">{% trans "Password forgotten" %}</a>
28 34
                     </div>
29 35
                 </form>
30 36
             </div>
@@ -34,10 +40,10 @@
34 40
     <div class="row">
35 41
         <div class="panel panel-danger">
36 42
             <div class="panel-heading">
37
-                <h2 class="panel-title">Erreur</h2>
43
+                <h2 class="panel-title">{% trans "Error" %}</h2>
38 44
             </div>
39 45
             <div class=" panel-body">
40
-                <p>T'arrives même pas à te connecter, seum.</p>
46
+                <p>{% trans "You cannot even login... seum!" %}</p>
41 47
             </div>
42 48
         </div>
43 49
     </div>
@@ -45,12 +51,12 @@
45 51
     <div class="row">
46 52
         <div class="panel panel-info">
47 53
             <div class="panel-heading">
48
-                <h2 class="panel-title">Pas encore de compteur ?</h2>
54
+                <h2 class="panel-title">{% trans "You don't have a counter yet?" %}</h2>
49 55
             </div>
50 56
             <div class=" panel-body">
51
-                <p>Dépếche toi d'aller le créer pour partager ton seum !</p>
57
+                <p>{% trans "Hurry up and create it to share your seum!" %}
52 58
                     <div class="text-center">
53
-                        <a href="{% url 'create_user' %}" class="btn btn-default btn-info">Créer un compteur</a>
59
+                        <a href="{% url 'create_user' %}" class="btn btn-default btn-info">{% trans "Create a counter" %}</a>
54 60
                     </div>
55 61
             </div>
56 62
         </div>

+ 14 - 8
counter/templates/passwordChange.html

@@ -1,4 +1,10 @@
1
-{% extends 'baseTemplate.html' %} {% block title %}Changement du mot de passe{% endblock %}{% block content %}
1
+{% extends 'baseTemplate.html' %}
2
+
3
+{% load i18n %}
4
+
5
+{% block title %}{% trans "Change password" %}{# Changement du mot de passe #}{% endblock %}
6
+
7
+{% block content %}
2 8
 <div class="container">
3 9
     <div class="row">
4 10
         <div class="text-center">
@@ -8,25 +14,25 @@
8 14
     <div class="row">
9 15
         <div class="panel panel-primary">
10 16
             <div class="panel-heading">
11
-                <h2 class="panel-title">Change ton mot de passe du seum !</h2>
17
+                <h2 class="panel-title">{% trans "Change your password of the seum!" %}</h2>
12 18
             </div>
13 19
             <div class=" panel-body">
14 20
                 <form class='' method="POST" action="{% url 'password_change' %}">
15 21
                     {% csrf_token %}
16 22
                     <div class="form-group">
17
-                        <label for="id_old_password">Ancien mot de passe</label>
23
+                        <label for="id_old_password">{% trans "Old password" %}</label>
18 24
                         <input id="id_old_password" type="password" class="form-control" name="old_password" required />
19 25
                     </div>
20 26
                     <div class="form-group">
21
-                        <label for="id_new_password1">Nouveau mot de passe</label>
27
+                        <label for="id_new_password1">{% trans "New password" %}</label>
22 28
                         <input id="id_new_password1" type="password" class="form-control" name="new_password1" required />
23 29
                     </div>
24 30
                     <div class="form-group">
25
-                        <label for="id_new_password2">Confirmer le nouveau mot de passe</label>
31
+                        <label for="id_new_password2">{% trans "Confirm new password" %}</label>
26 32
                         <input id="id_new_password2" type="password" class="form-control" name="new_password2" required />
27 33
                     </div>
28 34
                     <div class="text-center">
29
-                        <button type="submit" class="btn btn-default btn-success">Changer le mot de passe</button>
35
+                        <button type="submit" class="btn btn-default btn-success">{% trans "Change password" %}</button>
30 36
                     </div>
31 37
                 </form>
32 38
             </div>
@@ -36,10 +42,10 @@
36 42
     <div class="row">
37 43
         <div class="panel panel-danger">
38 44
             <div class="panel-heading">
39
-                <h2 class="panel-title">Erreur</h2>
45
+                <h2 class="panel-title">{% trans "Error" %}</h2>
40 46
             </div>
41 47
             <div class=" panel-body">
42
-                <p>T'arrives même pas à changer ton mot de passe, seum.</p>
48
+                <p>{% trans "You don't even manage to change your password, seum." %}{# T'arrives même pas à changer ton mot de passe, seum. #}</p>
43 49
             </div>
44 50
         </div>
45 51
     </div>

+ 10 - 4
counter/templates/passwordChangeDone.html

@@ -1,4 +1,10 @@
1
-{% extends 'baseTemplate.html' %} {% block title %}Mot de passe changé !{% endblock %}{% block content %}
1
+{% extends 'baseTemplate.html' %}
2
+
3
+{% load i18n %}
4
+
5
+{% block title %}{% trans "Password changed!" %}{% endblock %}
6
+
7
+{% block content %}
2 8
 <div class="container">
3 9
     <div class="row">
4 10
         <div class="text-center">
@@ -8,12 +14,12 @@
8 14
     <div class="row">
9 15
         <div class="panel panel-primary">
10 16
             <div class="panel-heading">
11
-                <h2 class="panel-title">Victoire !</h2>
17
+                <h2 class="panel-title">{% trans "Victory!" %}</h2>
12 18
             </div>
13 19
             <div class=" panel-body">
14
-                <p>T'as changé ton mot de passe !</p>
20
+                <p>{% trans "You changed your password!" %}{# T'as changé ton mot de passe ! #}</p>
15 21
                 <div class="text-center">
16
-                    <a href="{% url 'home' %}" type="submit" class="btn btn-default btn-success">Retour à l'accueil</a>
22
+                    <a href="{% url 'home' %}" type="submit" class="btn btn-default btn-success">{% trans "Home" %}</a>
17 23
                 </div>
18 24
             </div>
19 25
         </div>

+ 12 - 6
counter/templates/passwordReset.html

@@ -1,4 +1,10 @@
1
-{% extends 'baseTemplate.html' %} {% block title %}Mot de passe oublié{% endblock %}{% block content %}
1
+{% extends 'baseTemplate.html' %}
2
+
3
+{% load i18n %}
4
+
5
+{% block title %}{% trans "Password forgotten" %}{% endblock %}
6
+
7
+{% block content %}
2 8
 <div class="container">
3 9
     <div class="row">
4 10
         <div class="text-center">
@@ -8,17 +14,17 @@
8 14
     <div class="row">
9 15
         <div class="panel panel-primary">
10 16
             <div class="panel-heading">
11
-                <h2 class="panel-title">Réinitialisation du mot de passe</h2>
17
+                <h2 class="panel-title">{% trans "Reset password" %}</h2>
12 18
             </div>
13 19
             <div class=" panel-body">
14 20
                 <form class='' method="POST" action="{% url 'password_reset' %}">
15 21
                     {% csrf_token %}
16 22
                     <div class="form-group">
17
-                        <label for="id_email">Email auquel sera envoyé le lien de réinitialisation</label>
23
+                        <label for="id_email">{% trans "Email for the reinitialisation link" %}</label>
18 24
                         <input id="id_email" type="email" class="form-control" name="email" required/>
19 25
                     </div>
20 26
                     <div class="text-center">
21
-                        <button type="submit" class="btn btn-default btn-success">Envoyer</button>
27
+                        <button type="submit" class="btn btn-default btn-success">{% trans "Send" %}</button>
22 28
                     </div>
23 29
                 </form>
24 30
             </div>
@@ -28,10 +34,10 @@
28 34
     <div class="row">
29 35
         <div class="panel panel-danger">
30 36
             <div class="panel-heading">
31
-                <h2 class="panel-title">Erreur</h2>
37
+                <h2 class="panel-title">{% trans "Error" %}</h2>
32 38
             </div>
33 39
             <div class=" panel-body">
34
-                <p>T'arrives même pas à te connecter, seum.</p>
40
+                <p>{% trans "You cannot even login... seum!" %}</p>
35 41
             </div>
36 42
         </div>
37 43
     </div>

+ 10 - 4
counter/templates/passwordResetComplete.html

@@ -1,4 +1,10 @@
1
-{% extends 'baseTemplate.html' %} {% block title %}Mot de passe changé !{% endblock %}{% block content %}
1
+{% extends 'baseTemplate.html' %}
2
+
3
+{% load i18n %}
4
+
5
+{% block title %}{% trans "Your password has been reset" %}{% endblock %}
6
+
7
+{% block content %}
2 8
 <div class="container">
3 9
     <div class="row">
4 10
         <div class="text-center">
@@ -8,12 +14,12 @@
8 14
     <div class="row">
9 15
         <div class="panel panel-success">
10 16
             <div class="panel-heading">
11
-                <h2 class="panel-title">Victoire !</h2>
17
+                <h2 class="panel-title">{% trans "Victory!" %}</h2>
12 18
             </div>
13 19
             <div class=" panel-body">
14
-                <p>Ça y est tu as réinitialisé ton mot de passe ! Du coup tu peux aller remettre ton compteur à zéro.</p>
20
+                <p>{% trans "That's it! You have reset your password! You can reset your counter now." %}</p>
15 21
                 <div class="text-center">
16
-                    <a href="{% url 'home' %}" type="submit" class="btn btn-default btn-success">Retour à l'accueil</a>
22
+                    <a href="{% url 'home' %}" type="submit" class="btn btn-default btn-success">{% trans "Home" %}</a>
17 23
                 </div>
18 24
             </div>
19 25
         </div>

+ 20 - 10
counter/templates/passwordResetConfirm.html

@@ -1,4 +1,10 @@
1
-{% extends 'baseTemplate.html' %} {% block title %}Changement du mot de passe{% endblock %}{% block content %}
1
+{% extends 'baseTemplate.html' %}
2
+
3
+{% load i18n %}
4
+
5
+{% block title %}{% trans "Change password" %}{# Changement du mot de passe #}{% endblock %}
6
+
7
+{% block content %}
2 8
 <div class="container">
3 9
     <div class="row">
4 10
         <div class="text-center">
@@ -9,21 +15,25 @@
9 15
         {% if validlink %}
10 16
         <div class="panel panel-primary">
11 17
             <div class="panel-heading">
12
-                <h2 class="panel-title">Change ton mot de passe du seum !</h2>
18
+                <h2 class="panel-title">{% trans "Change your password of the seum!" %}</h2>
13 19
             </div>
14 20
             <div class=" panel-body">
15
-                <form class='' method="POST">
21
+                <form class='' method="POST" action="{% url 'password_change' %}">
16 22
                     {% csrf_token %}
17 23
                     <div class="form-group">
18
-                        <label for="id_new_password1">Nouveau mot de passe</label>
24
+                        <label for="id_old_password">{% trans "Old password" %}</label>
25
+                        <input id="id_old_password" type="password" class="form-control" name="old_password" required />
26
+                    </div>
27
+                    <div class="form-group">
28
+                        <label for="id_new_password1">{% trans "New password" %}</label>
19 29
                         <input id="id_new_password1" type="password" class="form-control" name="new_password1" required />
20 30
                     </div>
21 31
                     <div class="form-group">
22
-                        <label for="id_new_password2">Confirmer le nouveau mot de passe</label>
32
+                        <label for="id_new_password2">{% trans "Confirm new password" %}</label>
23 33
                         <input id="id_new_password2" type="password" class="form-control" name="new_password2" required />
24 34
                     </div>
25 35
                     <div class="text-center">
26
-                        <button type="submit" class="btn btn-default btn-success">Changer le mot de passe</button>
36
+                        <button type="submit" class="btn btn-default btn-success">{% trans "Change password" %}</button>
27 37
                     </div>
28 38
                 </form>
29 39
             </div>
@@ -33,10 +43,10 @@
33 43
     <div class="row">
34 44
         <div class="panel panel-danger">
35 45
             <div class="panel-heading">
36
-                <h2 class="panel-title">Erreur</h2>
46
+                <h2 class="panel-title">{% trans "Error" %}</h2>
37 47
             </div>
38 48
             <div class=" panel-body">
39
-                <p>T'arrives même pas à changer ton mot de passe, seum.</p>
49
+                <p>{% trans "You don't even manage to change your password, seum." %}</p>
40 50
             </div>
41 51
         </div>
42 52
     </div>
@@ -44,10 +54,10 @@
44 54
     <div class="row">
45 55
         <div class="panel panel-danger">
46 56
             <div class="panel-heading">
47
-                <h2 class="panel-title">Erreur</h2>
57
+                <h2 class="panel-title">{% trans "Error" %}</h2>
48 58
             </div>
49 59
             <div class=" panel-body">
50
-                <p>T'as le seum, le lien qui t'as été donné pour visiter cette page est déjà utilisé.</p>
60
+                <p>{% trans "You've got the seum, the link that we sent you has already been used." %}</p>
51 61
             </div>
52 62
         </div>
53 63
     </div>

+ 9 - 3
counter/templates/passwordResetDone.html

@@ -1,4 +1,10 @@
1
-{% extends 'baseTemplate.html' %} {% block title %}Mail envoyé !{% endblock %}{% block content %}
1
+{% extends 'baseTemplate.html' %}
2
+
3
+{% load i18n %}
4
+
5
+{% block title %}{% trans "Email sent!" %}{% endblock %}
6
+
7
+{% block content %}
2 8
 <div class="container">
3 9
     <div class="row">
4 10
         <div class="text-center">
@@ -8,10 +14,10 @@
8 14
     <div class="row">
9 15
         <div class="panel panel-success">
10 16
             <div class="panel-heading">
11
-                <h2 class="panel-title">Victoire !</h2>
17
+                <h2 class="panel-title">{% trans "Victory!" %}</h2>
12 18
             </div>
13 19
             <div class=" panel-body">
14
-                <p>Un mail t'a été envoyé, suis les instructions pour aller réinitialiser ton mot de passe.</p>
20
+                <p>{% trans "We have sent you an email. Follow the instructions to reinitialise your password." %}</p>
15 21
             </div>
16 22
         </div>
17 23
     </div>

+ 4 - 4
counter/templates/resetEmail.txt

@@ -1,11 +1,11 @@
1
-T'as perdu ton mot de passe et t'as le seum. Il te reste plus qu'à suivre le lien pour le réinitialiser :
1
+{% load i18n %}
2
+{% blocktrans %}You have lost your password and you've got the seum. You have to follow this link (or copy-paste it in your browser) to reinitialise it:{% endblocktrans %}
2 3
 {% block reset_link %}
3 4
 {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
4 5
 {% endblock %}
5
-
6
-Ton nom d'utilisateur c'est {{ user.username }} au cas où tu l'aurais oublié aussi.
6
+{% trans "Your login is {{ user.username }} in case you have forgotten it too." %}
7 7
 
8 8
 --
9 9
 SeumBook™ - http://seum.merigoux.ovh
10 10
 
11
-P.S. : Pour ne plus recevoir ces messages, envoie un mail à denis.merigoux@gmail.com
11
+{% trans "P.S.: If you don't want to receive these mails anymore, contact us at denis.merigoux@gmail.com" %}

+ 16 - 10
counter/urls.py

@@ -3,20 +3,26 @@ from counter.rss import SeumFeed
3 3
 from django.contrib.auth import views as auth_views
4 4
 from django.views.generic.base import RedirectView
5 5
 
6
-from . import views
6
+from .views import counter, hashtag, home, reset, user
7 7
 
8 8
 urlpatterns = [
9
-    url(r'^$', views.home, name='home'),
10
-    url(r'^reset-counter/$', views.resetCounter, name='reset-counter'),
11
-    url(r'^counter/(?P<id_counter>\d+)/$', views.counter, name='counter'),
12
-    url(r'^hashtag/(?P<keyword>.+)/$', views.hashtag, name='hashtag'),
13
-    url(r'^rss/$', SeumFeed()),
14
-    url(r'^create_user/$', views.createUser, name='create_user'),
15
-    url(r'^like/$', views.like, name='like'),
16
-    url(r'^toggle-notif/$', views.toggleEmailNotifications,
9
+    url(r'^$', home.index, name='home'),
10
+    url(r'^toggle-notif/$', home.toggleEmailNotifications,
17 11
         name='toggle_email_notifications'),
18
-    url(r'^toggle-sort-score/$', views.toggleScoreSorting,
12
+    url(r'^toggle-sort-score/$', home.toggleScoreSorting,
19 13
         name='toggle_sort_score'),
14
+
15
+    url(r'^reset-counter/$', counter.reset_counter, name='reset-counter'),
16
+    url(r'^counter/(?P<id_counter>\d+)/$', counter.get, name='counter'),
17
+
18
+    url(r'^hashtag/(?P<keyword>.+)/$', hashtag.get, name='hashtag'),
19
+
20
+    url(r'^like/$', reset.like, name='like'),
21
+
22
+    url(r'^create_user/$', user.create, name='create_user'),
23
+
24
+    url(r'^rss/$', SeumFeed()),
25
+
20 26
     url(r'^login/$', auth_views.login,
21 27
         {'template_name': 'login.html'},
22 28
         name='login'),

+ 0 - 550
counter/views.py

@@ -1,550 +0,0 @@
1
-from django.shortcuts import render
2
-from counter.models import Counter, Reset, Like, Keyword, Hashtag
3
-from django.contrib.auth.models import User
4
-from babel.dates import format_timedelta, format_datetime
5
-from datetime import datetime, timedelta
6
-from django import forms
7
-from django.http import HttpResponseRedirect
8
-from django.core.mail import EmailMessage
9
-from django.contrib.auth.decorators import login_required
10
-from django.core.urlresolvers import reverse
11
-from django.db import IntegrityError
12
-from graphos.renderers import gchart
13
-from django.template.loader import render_to_string
14
-from graphos.sources.simple import SimpleDataSource
15
-from graphos.sources.model import ModelDataSource
16
-import random
17
-import math
18
-import copy
19
-import functools
20
-from django.utils import timezone
21
-from counter.utils import parseSeumReason
22
-
23
-# Number of counters displayed on the home page's best seumeurs graph
24
-bestSeumeursNumber = 15
25
-
26
-
27
-@login_required
28
-def home(request):
29
-    # Used later to keep track of the maximum JSS
30
-    lastResets = []
31
-    no_seum_delta = timedelta.max
32
-
33
-    # First select our counter
34
-    try:
35
-        myCounter = Counter.objects.get(user__id=request.user.id)
36
-        lastReset = Reset.objects.filter(
37
-            counter=myCounter).order_by('-timestamp')
38
-        if (lastReset.count() == 0):
39
-            # This person never had the seum
40
-            myCounter.lastReset = Reset()
41
-            myCounter.lastReset.delta = no_seum_delta
42
-            myCounter.lastReset.noSeum = True
43
-        else:
44
-            myCounter.lastReset = lastReset[0]
45
-            myCounter.lastReset.noSeum = False
46
-            if (myCounter.lastReset.who is None or
47
-                    myCounter.lastReset.who.id == myCounter.id):
48
-                myCounter.lastReset.selfSeum = True
49
-            else:
50
-                myCounter.lastReset.selfSeum = False
51
-            myCounter.lastReset.delta = datetime.now(
52
-            ) - myCounter.lastReset.timestamp.replace(tzinfo=None)
53
-            likesMe = Like.objects.filter(
54
-                reset=myCounter.lastReset)
55
-            myCounter.likeCount = likesMe.count()
56
-            if myCounter.likeCount:
57
-                myCounter.likersString = functools.reduce(
58
-                    lambda a, b: a + ", " + b,
59
-                    [like.liker.trigramme for like in likesMe])
60
-        myCounter.lastReset.formatted_delta = format_timedelta(
61
-            myCounter.lastReset.delta, locale='fr', threshold=1)
62
-    except Counter.DoesNotExist:
63
-        return HttpResponseRedirect(reverse('login'))
64
-
65
-    # Building data for counters display
66
-    counters = Counter.objects.all()
67
-    for counter in counters:
68
-        # Only the last reset is displayed
69
-        lastReset = Reset.objects.filter(
70
-            counter=counter).order_by('-timestamp')
71
-        if (lastReset.count() == 0):  # This person never had the seum
72
-            counter.lastReset = Reset()
73
-            counter.lastReset.delta = no_seum_delta
74
-            counter.lastReset.noSeum = True
75
-            counter.CSSclass = "warning"
76
-            counter.likeCount = -1
77
-        else:  # This person already had the seum
78
-            counter.lastReset = lastReset[0]
79
-            # To display the last seum we have to know if it is self-inflicted
80
-            if (counter.lastReset.who is None or
81
-                    counter.lastReset.who.id == counter.id):
82
-                counter.lastReset.selfSeum = True
83
-            else:
84
-                counter.lastReset.selfSeum = False
85
-            # Now we compute the duration since the reset
86
-            counter.lastReset.noSeum = False
87
-            counter.lastReset.delta = datetime.now(
88
-            ) - counter.lastReset.timestamp.replace(tzinfo=None)
89
-            # Defining CSS attributes for the counter
90
-            if counter.id == myCounter.id:
91
-                counter.CSSclass = 'primary'
92
-            else:
93
-                counter.CSSclass = 'default'
94
-            # Computing the total number of likes for this counter
95
-            likesMe = Like.objects.filter(
96
-                reset=counter.lastReset)
97
-            counter.likeCount = likesMe.count()
98
-            counter.alreadyLiked = (Like.objects.filter(
99
-                reset=counter.lastReset, liker=myCounter).exists())
100
-            if counter.likeCount > 0:
101
-                counter.likersString = functools.reduce(
102
-                    lambda a, b: a + ", " + b,
103
-                    [like.liker.trigramme for like in likesMe])
104
-
105
-        counter.lastReset.formatted_delta = format_timedelta(
106
-            counter.lastReset.delta, locale='fr', threshold=1)
107
-        counter.isHidden = 'hidden'
108
-
109
-    if myCounter.sort_by_score:
110
-        # Now we sort the counters according to a reddit-like ranking formula
111
-        # We take into account the number of likes of a reset and recentness
112
-        # The log on the score will give increased value to the first likes
113
-        # The counters with no seum have a like count of -1 by convention
114
-        counters = sorted(counters, key=lambda t: - (
115
-            math.log(t.likeCount + 2) /
116
-            (1 + (t.lastReset.delta.total_seconds()) /
117
-             (24 * 3600))))
118
-    else:
119
-        counters = sorted(counters, key=lambda t: +
120
-                          t.lastReset.delta.total_seconds())
121
-
122
-    # Timeline graph
123
-    resets = Reset.objects.filter(
124
-        timestamp__gte=timezone.now() - timedelta(days=1))
125
-    if (resets.count() == 0):
126
-        noTimeline = True
127
-        line_chart = None
128
-    else:
129
-        noTimeline = False
130
-        for reset in resets:
131
-            reset.timestamp = {
132
-                'v': reset.timestamp.timestamp(),
133
-                'f': "Il y a " + format_timedelta(datetime.now() -
134
-                                                  reset.timestamp.replace(
135
-                                                      tzinfo=None),
136
-                                                  locale='fr', threshold=1)
137
-            }
138
-            if (reset.who is None or
139
-                    reset.who.id == reset.counter.id):
140
-                reset.Seum = {'v': 0,
141
-                              'f': reset.counter.trigramme +
142
-                              " : " + reset.reason}
143
-            else:
144
-                reset.Seum = {'v': 0,
145
-                              'f': reset.who.trigramme + ' à ' +
146
-                              reset.counter.trigramme +
147
-                              " : " + reset.reason}
148
-        line_data = ModelDataSource(resets, fields=['timestamp', 'Seum'])
149
-        line_chart = gchart.LineChart(line_data, options={
150
-            'lineWidth': 0,
151
-            'pointSize': 10,
152
-            'title': '',
153
-            'vAxis': {'ticks': []},
154
-            'hAxis': {
155
-                'ticks': [
156
-                    {'v': (datetime.now() - timedelta(days=1)
157
-                           ).timestamp(), 'f': 'Il y a 24 h'},
158
-                    {'v': datetime.now().timestamp(), 'f': 'Présent'}
159
-                ]
160
-            },
161
-            'legend': 'none',
162
-            'height': 90
163
-        })
164
-
165
-    # Graph of greatest seumers
166
-    seumCounts = []
167
-    for counter in counters:
168
-        seumCounts.append([counter.trigramme, Reset.objects.filter(
169
-            counter=counter).count()])
170
-    if (len(seumCounts) == 0):
171
-        noBestSeum = True
172
-        best_chart = None
173
-    else:
174
-        seumCounts.sort(key=lambda x: -x[1])
175
-        noBestSeum = False
176
-        seumCounts.insert(0, ['Trigramme', 'Nombre de seums'])
177
-        best_data = SimpleDataSource(seumCounts[:bestSeumeursNumber])
178
-        best_chart = gchart.ColumnChart(best_data, options={
179
-            'title': '',
180
-            'legend': 'none',
181
-            'vAxis': {'title': 'Nombre de seums'},
182
-            'hAxis': {'title': 'Trigramme'},
183
-        })
184
-
185
-    # Graph of seum activity
186
-    resets = Reset.objects.filter(
187
-        timestamp__gte=timezone.now() - timedelta(days=365))
188
-    months = {}
189
-    for reset in resets:
190
-        monthDate = datetime(reset.timestamp.year, reset.timestamp.month, 1)
191
-        months[monthDate] = months.get(monthDate, 0) + 1
192
-
193
-    monthList = sorted(months.items(), key=lambda t: t[0])
194
-    seumActivity = []
195
-    for month in monthList:
196
-        seumActivity.append(
197
-            [format_datetime(month[0], locale='fr',
198
-                             format="MMM Y").capitalize(), month[1]])
199
-    if (len(seumActivity) == 0):
200
-        noSeumActivity = True
201
-        activity_chart = None
202
-    else:
203
-        noSeumActivity = False
204
-        seumActivity.insert(0, ['Mois', 'Nombre de seums'])
205
-        activity_data = SimpleDataSource(seumActivity)
206
-        activity_chart = gchart.ColumnChart(activity_data, options={
207
-            'title': '',
208
-            'legend': 'none',
209
-            'vAxis': {'title': 'Nombre de seums'},
210
-            'hAxis': {'title': 'Mois'},
211
-        })
212
-
213
-    # Graph of best likers
214
-    likersCounts = []
215
-    for counter in counters:
216
-        likersCounts.append(
217
-            [counter.trigramme, Like.objects.filter(liker=counter).count()])
218
-    if (len(likersCounts) == 0):
219
-        noBestLikers = True
220
-        likers_chart = None
221
-    else:
222
-        likersCounts.sort(key=lambda x: -x[1])
223
-        noBestLikers = False
224
-        likersCounts.insert(0, ['Trigramme', 'Nombre de likes distribués'])
225
-        likers_data = SimpleDataSource(likersCounts[:bestSeumeursNumber])
226
-        likers_chart = gchart.ColumnChart(likers_data, options={
227
-            'title': '',
228
-            'legend': 'none',
229
-            'vAxis': {'title': 'Nombre de likes distribués'},
230
-            'hAxis': {'title': 'Trigramme'},
231
-        })
232
-
233
-    # Graph of popular hashtags
234
-    hashtagsCounts = []
235
-    keywords = Keyword.objects.all()
236
-    for keyword in keywords:
237
-        hashtagsCounts.append(
238
-            ['#' + keyword.text,
239
-             Hashtag.objects.filter(keyword=keyword).count()])
240
-    if (len(hashtagsCounts) == 0):
241
-        noBestHashtags = True
242
-        hashtags_chart = None
243
-    else:
244
-        hashtagsCounts.sort(key=lambda x: -x[1])
245
-        noBestHashtags = False
246
-        hashtagsCounts.insert(0, ['Trigramme', 'Nombre de likes distribués'])
247
-        hashtags_data = SimpleDataSource(hashtagsCounts[:bestSeumeursNumber])
248
-        hashtags_chart = gchart.ColumnChart(hashtags_data, options={
249
-            'title': '',
250
-            'legend': 'none',
251
-            'vAxis': {'title': 'Nombre de seums contenant le hashtag'},
252
-            'hAxis': {'title': 'Hashtag'},
253
-        })
254
-
255
-    # Graph of best likee
256
-    likeesCounts = []
257
-    for counter in counters:
258
-        likeesCounts.append(
259
-            [counter.trigramme,
260
-             Like.objects.filter(reset__counter=counter).count()])
261
-    if (len(likeesCounts) == 0):
262
-        noBestLikees = True
263
-        likees_chart = None
264
-    else:
265
-        likeesCounts.sort(key=lambda x: -x[1])
266
-        noBestLikees = False
267
-        likeesCounts.insert(0, ['Trigramme', 'Nombre de likes reçus'])
268
-        likees_data = SimpleDataSource(likeesCounts[:bestSeumeursNumber])
269
-        likees_chart = gchart.ColumnChart(likees_data, options={
270
-            'title': '',
271
-            'legend': 'none',
272
-            'vAxis': {'title': 'Nombre de likes reçus'},
273
-            'hAxis': {'title': 'Trigramme'},
274
-        })
275
-
276
-    # At last we render the page
277
-    return render(request, 'homeTemplate.html', {
278
-        'counters': counters,
279
-        'line_chart': line_chart,
280
-        'best_chart': best_chart,
281
-        'likers_chart': likers_chart,
282
-        'likees_chart': likees_chart,
283
-        'hashtags_chart': hashtags_chart,
284
-        'activity_chart': activity_chart,
285
-        'noTimeline': noTimeline,
286
-        'noBestSeum': noBestSeum,
287
-        'noBestLikers': noBestLikers,
288
-        'noBestLikees': noBestLikees,
289
-        'noBestHashtags': noBestHashtags,
290
-        'noSeumActivity': noSeumActivity,
291
-        'myCounter': myCounter,
292
-    })
293
-
294
-
295
-@login_required
296
-def resetCounter(request):
297
-    # Update Form counter
298
-    if (request.method == 'POST'):
299
-        # create a form instance and populate it with data from the request:
300
-        data = dict(request.POST)
301
-
302
-        who = Counter.objects.get(pk=int(data['who'][0]))
303
-        reason = data['reason'][0]
304
-        if 'counter' in data.keys():
305
-            counter = Counter.objects.get(pk=int(data['counter'][0]))
306
-        else:
307
-            try:
308
-                counter = Counter.objects.get(trigramme=data['trigramme'][0])
309
-            except Counter.DoesNotExist:
310
-                return HttpResponseRedirect(data['redirect'][0])
311
-        reset = Reset()
312
-        reset.counter = counter
313
-        reset.who = who
314
-        reset.reason = data['reason'][0]
315
-        reset.timestamp = datetime.now()
316
-
317
-        # we check that the seumer is the autenticated user
318
-        if (reset.who.user is None or
319
-                reset.who.user.id != request.user.id):
320
-            return HttpResponseRedirect(data['redirect'][0])
321
-
322
-        reset.save()
323
-
324
-        # Now we deal with the hashtags
325
-        keywords = parseSeumReason(reason)
326
-        for keyword in keywords:
327
-            hashtag = Hashtag(reset=reset, keyword=keyword)
328
-            hashtag.save()
329
-
330
-        # We send the emails only to those who want
331
-        emails = [u.email for u in Counter.objects.all()
332
-                  if u.email_notifications]
333
-        # Now send emails to everyone
334
-        if (reset.who is None or
335
-                reset.who.id == counter.id):
336
-            selfSeum = True
337
-        else:
338
-            selfSeum = False
339
-        text_of_email = render_to_string(
340
-            'seumEmail.txt', {'reason': data['reason'][0],
341
-                              'name': counter.name,
342
-                              'who': reset.who,
343
-                              'selfSeum': selfSeum,
344
-                              })
345
-        email_to_send = EmailMessage(
346
-            '[SeumBook] ' + counter.trigramme + ' a le seum',
347
-            text_of_email,
348
-            'SeumMan <seum@merigoux.ovh>', emails, [],
349
-            reply_to=emails)
350
-        email_to_send.send(fail_silently=True)
351
-
352
-    return HttpResponseRedirect(data['redirect'][0])
353
-
354
-
355
-@login_required
356
-def counter(request, id_counter):
357
-    try:
358
-        myCounter = Counter.objects.get(user__id=request.user.id)
359
-    except Counter.DoesNotExist:
360
-        return HttpResponseRedirect(reverse('login'))
361
-
362
-    counter = Counter.objects.get(pk=id_counter)
363
-    resets = Reset.objects.filter(counter=counter).order_by('-timestamp')
364
-    timezero = timedelta(0)
365
-    # Display
366
-    if (resets.count() == 0):
367
-        counter.lastReset = Reset()
368
-        counter.lastReset.delta = timezero
369
-        counter.lastReset.noSeum = True
370
-        seumFrequency = 'inconnu'
371
-    else:
372
-        firstReset = copy.copy(resets[len(resets) - 1])
373
-        counter.lastReset = resets[0]
374
-        counter.lastReset.noSeum = False
375
-        if (counter.lastReset.who is None or
376
-                counter.lastReset.who.id == counter.id):
377
-            counter.lastReset.selfSeum = True
378
-        else:
379
-            counter.lastReset.selfSeum = False
380
-        counter.lastReset.delta = datetime.now(
381
-        ) - counter.lastReset.timestamp.replace(tzinfo=None)
382
-        counter.lastReset.formatted_delta = format_timedelta(
383
-            counter.lastReset.delta, locale='fr', threshold=1)
384
-        counter.seumCount = Reset.objects.filter(
385
-            counter=counter).count()
386
-        seumFrequency = format_timedelta((
387
-            datetime.now() - firstReset.timestamp.replace(tzinfo=None)) /
388
-            counter.seumCount, locale='fr', threshold=1)
389
-        counter.alreadyLiked = (Like.objects.filter(
390
-            reset=counter.lastReset, liker=myCounter).exists())
391
-        likesMe = Like.objects.filter(
392
-            reset=counter.lastReset)
393
-        counter.likeCount = likesMe.count()
394
-        if counter.likeCount > 0:
395
-            counter.likersString = functools.reduce(
396
-                lambda a, b: a + ", " + b,
397
-                [like.liker.trigramme for like in likesMe])
398
-
399
-    for reset in resets:
400
-        if (reset.who is None or
401
-                reset.who.id == reset.counter.id):
402
-            reset.selfSeum = True
403
-        else:
404
-            reset.selfSeum = False
405
-        reset.date = format_datetime(
406
-            reset.timestamp, locale='fr',
407
-            format="dd/MM/Y HH:mm")
408
-        reset.likeCount = Like.objects.filter(reset=reset).count()
409
-
410
-    # Timeline graph
411
-    # Data pre-processing
412
-    if not counter.lastReset.noSeum:
413
-        resets_graph = resets
414
-        for reset in resets_graph:
415
-            reset.timestamp = {
416
-                'v': reset.timestamp.timestamp(),
417
-                'f': "Il y a " + format_timedelta(
418
-                    datetime.now() - reset.timestamp.replace(tzinfo=None),
419
-                    locale='fr', threshold=1)
420
-            }
421
-            if reset.selfSeum:
422
-                reset.Seum = {'v': 0, 'f': reset.reason}
423
-            else:
424
-                reset.Seum = {'v': 0, 'f': 'De ' +
425
-                              reset.who.trigramme + ' : ' + reset.reason}
426
-        # Drawing the graph
427
-        data = ModelDataSource(
428
-            resets, fields=['timestamp', 'Seum'])
429
-        chart = gchart.LineChart(data, options={
430
-            'lineWidth': 0,
431
-            'pointSize': 10,
432
-            'title': '',
433
-            'vAxis': {'ticks': []},
434
-            'hAxis': {'ticks': [{
435
-                'v': firstReset.timestamp.timestamp(),
436
-                'f': 'Il y a ' + format_timedelta(
437
-                    datetime.now() - firstReset.timestamp.replace(tzinfo=None),
438
-                    locale='fr', threshold=1)
439
-            }, {
440
-                'v': datetime.now().timestamp(),
441
-                'f': 'Présent'}
442
-            ]},
443
-            'legend': 'none',
444
-            'height': 90
445
-        })
446
-    else:
447
-        chart = None
448
-
449
-    return render(request, 'counterTemplate.html', {
450
-        'counter': counter,
451
-        'chart': chart,
452
-        'resets': resets,
453
-        'seumFrequency': seumFrequency,
454
-        'myCounter': myCounter,
455
-    })
456
-
457
-
458
-@login_required
459
-def hashtag(request, keyword):
460
-    try:
461
-        keyword = Keyword.objects.get(text=keyword)
462
-    except Keyword.DoesNotExist:
463
-        print('erreur !')
464
-        return HttpResponseRedirect(reverse('home'))
465
-    hashtag = '#' + keyword.text
466
-    resets = Reset.objects.filter(
467
-        hashtag__keyword=keyword).order_by('-timestamp')
468
-    for reset in resets:
469
-        if (reset.who is None or
470
-                reset.who.id == reset.counter.id):
471
-            reset.selfSeum = True
472
-        else:
473
-            reset.selfSeum = False
474
-        reset.date = format_datetime(
475
-            reset.timestamp, locale='fr',
476
-            format="dd/MM/Y HH:mm")
477
-        reset.likeCount = Like.objects.filter(reset=reset).count()
478
-    return render(request, 'hashtagTemplate.html', {
479
-        'hashtag': hashtag,
480
-        'totalNumber': resets.count(),
481
-        'resets': resets,
482
-    })
483
-
484
-
485
-def createUser(request):
486
-    if (request.method == 'POST'):
487
-        # create a form instance and populate it with data from the request:
488
-        data = dict(request.POST)
489
-        email = data['email'][0]
490
-        username = email.split('@')[0]
491
-        trigramme = data['trigramme'][0]
492
-        nick = data['nick'][0]
493
-        password1 = data['password1'][0]
494
-        password2 = data['password2'][0]
495
-        email_notifications = ('email_notifications' in data.keys())
496
-
497
-        if password1 != password2:
498
-            error = "Les deux mots de passe sont différents."
499
-            return render(request, 'createUser.html', {'error': error})
500
-        try:
501
-            test_user = User.objects.get(email=email)
502
-            error = "Un utilisateur avec cette adresse email existe déjà !"
503
-            return render(request, 'createUser.html', {'error': error})
504
-        except User.DoesNotExist:
505
-            try:
506
-                user = User.objects.create_user(username, email, password1)
507
-            except IntegrityError:
508
-                error = "Utilise une autre adresse email, un autre utilisateur \
509
-                 a le même login que toi."
510
-                return render(request, 'createUser.html', {'error': error})
511
-            counter = Counter()
512
-            counter.name = nick
513
-            counter.email = email
514
-            counter.trigramme = trigramme
515
-            counter.user = user
516
-            counter.email_notifications = False
517
-            counter.save()
518
-            return render(request, 'createUserDone.html', {'login': username})
519
-    else:
520
-        return render(request, 'createUser.html', {'error': None})
521
-
522
-
523
-@login_required
524
-def toggleEmailNotifications(request):
525
-    counter = Counter.objects.get(user=request.user)
526
-    counter.email_notifications = not counter.email_notifications
527
-    counter.save()
528
-    return HttpResponseRedirect(reverse('home'))
529
-
530
-
531
-@login_required
532
-def toggleScoreSorting(request):
533
-    counter = Counter.objects.get(user=request.user)
534
-    counter.sort_by_score = not counter.sort_by_score
535
-    counter.save()
536
-    return HttpResponseRedirect(reverse('home'))
537
-
538
-
539
-@login_required
540
-def like(request):
541
-    if (request.method == 'POST'):
542
-        # create a form instance and populate it with data from the request:
543
-        data = dict(request.POST)
544
-        liker = Counter.objects.get(pk=data['liker'][0])
545
-        reset = Reset.objects.get(pk=data['reset'][0])
546
-        like = Like()
547
-        like.liker = liker
548
-        like.reset = reset
549
-        like.save()
550
-    return HttpResponseRedirect(data['redirect'][0])

+ 161 - 0
counter/views/counter.py

@@ -0,0 +1,161 @@
1
+from datetime import datetime, timedelta
2
+from copy import copy
3
+
4
+from django.contrib.auth.decorators import login_required
5
+from django.core.mail import EmailMessage
6
+from django.core.urlresolvers import reverse
7
+from django.db.models import Count, Prefetch
8
+from django.http import HttpResponseRedirect
9
+from django.shortcuts import render
10
+from django.template.loader import render_to_string
11
+from django.utils.translation import ugettext as _, get_language
12
+
13
+import arrow
14
+from babel.dates import format_timedelta, format_datetime
15
+from graphos.renderers import gchart
16
+from graphos.sources.model import ModelDataSource
17
+
18
+from counter.models import *
19
+from counter.utils import parseSeumReason
20
+
21
+
22
+@login_required
23
+def get(request, id_counter):
24
+    try:
25
+        myCounter = Counter.objects.get(user__id=request.user.id)
26
+    except Counter.DoesNotExist:
27
+        return HttpResponseRedirect(reverse('login'))
28
+
29
+    counter = Counter.objects.prefetch_related(
30
+        # we get the related resets annotated with their number of likes
31
+        Prefetch('resets', queryset=Reset.objects.select_related('who', 'counter').annotate(likes_count=Count('likes'))),
32
+        'resets__likes'
33
+    ).get(pk=id_counter)
34
+    resets = list(counter.resets.order_by('-timestamp'))
35
+
36
+    # Display
37
+    if len(resets) == 0:
38
+        counter.lastReset = Reset()
39
+        counter.lastReset.delta = timedelta(0)
40
+        counter.lastReset.noSeum = True
41
+        seumFrequency = _('unknown')
42
+    else:
43
+        firstReset = copy(resets[-1])
44
+        counter.lastReset = resets[0]
45
+        counter.lastReset.noSeum = False
46
+        if counter.lastReset.who is None or counter.lastReset.who == counter:
47
+            counter.lastReset.selfSeum = True
48
+        else:
49
+            counter.lastReset.selfSeum = False
50
+
51
+        counter.lastReset.formatted_delta = arrow.Arrow.fromdatetime(counter.lastReset.timestamp).humanize(locale=get_language())
52
+        counter.seumCount = len(resets)
53
+        seumFrequency = format_timedelta((datetime.now() - firstReset.timestamp.replace(tzinfo=None)) / counter.seumCount,
54
+            locale=get_language(), threshold=1)
55
+
56
+        counter.lastLikes = list(counter.lastReset.likes.all())
57
+        counter.alreadyLiked = myCounter.id in [l.liker.id for l in counter.lastLikes]
58
+        counter.likes_count = len(counter.lastLikes)
59
+        if counter.likes_count > 0:
60
+            counter.likersString = ", ".join(like.liker.trigramme for like in counter.lastLikes)
61
+
62
+    for reset in resets:
63
+        if reset.who is None or reset.who.id == reset.counter.id:
64
+            reset.selfSeum = True
65
+        else:
66
+            reset.selfSeum = False
67
+        reset.date = reset.timestamp
68
+
69
+    # Timeline graph
70
+    # Data pre-processing
71
+    if not counter.lastReset.noSeum:
72
+        resets_graph = resets
73
+        for reset in resets_graph:
74
+            reset.timestamp = {
75
+                'v': reset.timestamp.timestamp(),
76
+                'f': arrow.Arrow.fromdatetime(reset.timestamp).humanize(locale=get_language())
77
+            }
78
+            if reset.selfSeum:
79
+                reset.Seum = {'v': 0, 'f': reset.reason}
80
+            else:
81
+                reset.Seum = {'v': 0, 'f': _('From %(who)s: %(reason)s') % {'who': reset.who.trigramme, 'reason': reset.reason}}
82
+
83
+        # Drawing the graph
84
+        data = ModelDataSource(resets, fields=['timestamp', 'Seum'])
85
+        chart = gchart.LineChart(data, options={
86
+            'lineWidth': 0,
87
+            'pointSize': 10,
88
+            'title': '',
89
+            'vAxis': {'ticks': []},
90
+            'hAxis': {'ticks': [{
91
+                'v': firstReset.timestamp.timestamp(),
92
+                'f': arrow.Arrow.fromdatetime(firstReset.timestamp).humanize(locale=get_language())
93
+            }, {
94
+                'v': datetime.now().timestamp(),
95
+                'f': 'Présent'}
96
+            ]},
97
+            'legend': 'none',
98
+            'height': 90
99
+        })
100
+    else:
101
+        chart = None
102
+
103
+    return render(request, 'counterTemplate.html', {
104
+        'counter': counter,
105
+        'chart': chart,
106
+        'resets': resets,
107
+        'seumFrequency': seumFrequency,
108
+        'myCounter': myCounter,
109
+    })
110
+
111
+
112
+@login_required
113
+def reset_counter(request):
114
+    # Update Form counter
115
+    if request.method == 'POST':
116
+        # create a form instance and populate it with data from the request:
117
+        data = dict(request.POST)
118
+
119
+        who = Counter.objects.get(pk=int(data['who'][0]))
120
+        reason = data['reason'][0]
121
+        if 'counter' in data.keys():
122
+            counter = Counter.objects.get(pk=int(data['counter'][0]))
123
+        else:
124
+            try:
125
+                counter = Counter.objects.get(trigramme=data['trigramme'][0])
126
+            except Counter.DoesNotExist:
127
+                return HttpResponseRedirect(data['redirect'][0])
128
+
129
+        reset = Reset(counter=counter, who=who, reason=data['reason'][0])
130
+
131
+        # we check that the seumer is the autenticated user
132
+        if reset.who.user is None or reset.who.user != request.user:
133
+            return HttpResponseRedirect(data['redirect'][0])
134
+
135
+        reset.save()
136
+
137
+        # Now we deal with the hashtags
138
+        keywords = parseSeumReason(reason)
139
+        Hashtag.objects.bulk_create([Hashtag(reset=reset, keyword=keyword) for keyword in keywords])
140
+
141
+        # We send the emails only to those who want
142
+        emails = [u['email'] for u in Counter.objects.filter(email_notifications=True).values('email')]
143
+        # Now send emails to everyone
144
+        if reset.who is None or reset.who == counter:
145
+            selfSeum = True
146
+        else:
147
+            selfSeum = False
148
+        text_of_email = render_to_string(
149
+            'seumEmail.txt', {'reason': data['reason'][0],
150
+                              'name': counter.name,
151
+                              'who': reset.who,
152
+                              'selfSeum': selfSeum,
153
+                              })
154
+        email_to_send = EmailMessage(
155
+            '[SeumBook] ' + counter.trigramme + ' a le seum',
156
+            text_of_email,
157
+            'SeumMan <seum@merigoux.ovh>', emails, [],
158
+            reply_to=emails)
159
+        email_to_send.send(fail_silently=True)
160
+
161
+    return HttpResponseRedirect(data['redirect'][0])

+ 36 - 0
counter/views/hashtag.py

@@ -0,0 +1,36 @@
1
+from django.contrib.auth.decorators import login_required
2
+from django.core.urlresolvers import reverse
3
+from django.http import HttpResponseRedirect
4
+from django.shortcuts import render
5
+from django.utils.translation import ugettext as _, get_language
6
+
7
+import arrow
8
+from babel.dates import format_timedelta, format_datetime
9
+
10
+from counter.models import *
11
+
12
+
13
+@login_required
14
+def get(request, keyword):
15
+    try:
16
+        keyword = Keyword.objects.get(text=keyword)
17
+    except Keyword.DoesNotExist:
18
+        print('erreur !')
19
+        return HttpResponseRedirect(reverse('home'))
20
+
21
+    hashtag = '#' + keyword.text
22
+    resets = Reset.objects.prefetch_related('likes', 'who', 'counter').filter(hashtags__keyword=keyword).order_by('-timestamp')
23
+    totalNumber = resets.count()
24
+    cur_lang = get_language()
25
+
26
+    for reset in resets:
27
+        if reset.who is None or reset.who == reset.counter:
28
+            reset.selfSeum = True
29
+        else:
30
+            reset.selfSeum = False
31
+        reset.likeCount = reset.likes.count()
32
+    return render(request, 'hashtagTemplate.html', {
33
+        'hashtag': hashtag,
34
+        'totalNumber': totalNumber,
35
+        'resets': resets,
36
+    })

+ 325 - 0
counter/views/home.py

@@ -0,0 +1,325 @@
1
+import copy
2
+from datetime import datetime, timedelta
3
+import math
4
+
5
+from django import forms
6
+from django.contrib.auth.decorators import login_required
7
+from django.contrib.auth.models import User
8
+from django.core.mail import EmailMessage
9
+from django.core.urlresolvers import reverse
10
+from django.db import IntegrityError
11
+from django.db.models import Prefetch, Count
12
+from django.http import HttpResponseRedirect
13
+from django.shortcuts import render
14
+from django.template.loader import render_to_string
15
+from django.utils import timezone
16
+from django.utils.translation import ugettext as _, get_language
17
+
18
+import arrow
19
+from babel.dates import format_timedelta, format_datetime
20
+from graphos.renderers import gchart
21
+from graphos.sources.model import ModelDataSource
22
+from graphos.sources.simple import SimpleDataSource
23
+import numpy as np
24
+import pandas as pd
25
+
26
+from counter.models import *
27
+from counter.utils import parseSeumReason
28
+
29
+
30
+# Number of counters displayed on the home page's best seumeurs graph
31
+bestSeumeursNumber = 15
32
+
33
+
34
+@login_required
35
+def index(request):
36
+    # Used later to keep track of the maximum JSS
37
+    lastResets = []
38
+    no_seum_delta = timedelta.max
39
+
40
+    # First select our counter
41
+    try:
42
+        myCounter = Counter.objects.get(user__id=request.user.id)
43
+        myLastReset = Reset.objects.select_related('who').filter(counter=myCounter).order_by('-timestamp').first()
44
+
45
+        if myLastReset is None:
46
+            # This person never had the seum
47
+            myCounter.lastReset = Reset()
48
+            myCounter.lastReset.delta = no_seum_delta
49
+            myCounter.lastReset.formatted_delta = format_timedelta(myCounter.lastReset.delta, locale=get_language(), threshold=1)
50
+            myCounter.lastReset.noSeum = True
51
+        else:
52
+            myCounter.lastReset = myLastReset
53
+            myCounter.lastReset.noSeum = False
54
+            if myCounter.lastReset.who is None or myCounter.lastReset.who.id == myCounter.id:
55
+                myCounter.lastReset.selfSeum = True
56
+            else:
57
+                myCounter.lastReset.selfSeum = False
58
+            likesMe = list(Like.objects.select_related('liker').filter(reset=myCounter.lastReset))
59
+            myCounter.likeCount = len(likesMe)
60
+            if myCounter.likeCount > 0:
61
+                myCounter.likersString = ", ".join([like.liker.trigramme for like in likesMe])
62
+            myCounter.lastReset.formatted_delta = arrow.Arrow.fromdatetime(myCounter.lastReset.timestamp).humanize(locale=get_language())
63
+
64
+    except Counter.DoesNotExist:
65
+        return HttpResponseRedirect(reverse('login'))
66
+
67
+    # Building data for counters display
68
+    counters = Counter.objects.prefetch_related(
69
+        'resets__likes',
70
+        Prefetch(
71
+            'resets',
72
+            queryset=Reset.objects.prefetch_related('who', Prefetch('likes', queryset=Like.objects.select_related('liker'))).order_by('-timestamp'),
73
+            to_attr='lastReset'
74
+        )
75
+    )
76
+    for counter in counters:
77
+        # Only the last reset is displayed
78
+        lastReset = list(counter.lastReset)
79
+        if len(lastReset) == 0:  # This person never had the seum
80
+            counter.lastReset = Reset()
81
+            counter.lastReset.delta = no_seum_delta
82
+            counter.lastReset.formatted_delta = format_timedelta(counter.lastReset.delta, locale=get_language(), threshold=1)
83
+            counter.lastReset.noSeum = True
84
+            counter.lastReset.likes_count = -1
85
+            counter.CSSclass = "warning"
86
+        else:  # This person already had the seum
87
+            counter.lastReset = lastReset[0]
88
+            # To display the last seum we have to know if it is self-inflicted
89
+            if counter.lastReset.who is None or counter.lastReset.who == counter:
90
+                counter.lastReset.selfSeum = True
91
+            else:
92
+                counter.lastReset.selfSeum = False
93
+            # Now we compute the duration since the reset
94
+            counter.lastReset.noSeum = False
95
+            counter.lastReset.delta = datetime.now() - counter.lastReset.timestamp.replace(tzinfo=None)
96
+            # Defining CSS attributes for the counter
97
+            counter.CSSclass = 'primary' if counter == myCounter else 'default'
98
+            # Computing the total number of likes for this counter
99
+            likesMe = list(counter.lastReset.likes.all())
100
+            counter.lastReset.likes_count = len(likesMe)
101
+            counter.alreadyLiked = myCounter in likesMe
102
+            if counter.lastReset.likes_count > 0:
103
+                counter.likersString = ", ".join([like.liker.trigramme for like in likesMe])
104
+            counter.lastReset.formatted_delta = arrow.Arrow.fromdatetime(counter.lastReset.timestamp).humanize(locale=get_language())
105
+
106
+        counter.likeCount = counter.lastReset.likes_count
107
+        counter.isHidden = 'hidden'
108
+
109
+    if myCounter.sort_by_score:
110
+        # Now we sort the counters according to a reddit-like ranking formula
111
+        # We take into account the number of likes of a reset and recentness
112
+        # The log on the score will give increased value to the first likes
113
+        # The counters with no seum have a like count of -1 by convention
114
+        sorting_key = lambda t: - (math.log(t.lastReset.likes_count + 2) / (1 + (t.lastReset.delta.total_seconds()) / (24 * 3600)))
115
+        counters = sorted(counters, key=sorting_key)
116
+    else:
117
+        counters = sorted(counters, key=lambda t: + t.lastReset.delta.total_seconds())
118
+
119
+
120
+    # ### GRAPHS ###
121
+    resets_raw = list(Reset.objects.select_related('who', 'counter').annotate(likes_count=Count('likes')))
122
+    likes_raw = list(Like.objects.select_related('liker', 'reset__counter').all())
123
+    hashtags_raw = list(Hashtag.objects.select_related('keyword').all())
124
+    # Prepare pandas.DataFrames to efficiently process the data
125
+    # About the counters
126
+    resets_cols = ['date', 'counter', 'counter_trigram', 'who', 'who_trigram', 'reason', 'likes_count']
127
+    resets_data = [[r.timestamp, r.counter.id, r.counter.trigramme, r.who, r.who, r.reason, r.likes_count] for r in resets_raw]
128
+    for r in resets_data:
129
+        r[3] = 0 if r[3] is None else r[3].id
130
+        r[4] = '' if r[4] is None else r[4].trigramme
131
+    resets_df = pd.DataFrame(resets_data, columns=resets_cols)
132
+    resets_df['timestamp'] = resets_df.date.map(lambda d: d.timestamp())
133
+    resets_df['self_seum'] = (resets_df.who.eq(np.zeros(resets_df.shape[0])) | resets_df.who.eq(resets_df.counter)).map(float)
134
+    resets_df['formatted_delta'] = resets_df.date.map(lambda d: arrow.Arrow.fromdatetime(d).humanize(locale=get_language()))
135
+    # About the likes
136
+    likes_cols = ['liker', 'liker_trigram', 'counter', 'counter_trigram']
137
+    likes_data = [[l.liker.id, l.liker.trigramme, l.reset.counter.id, l.reset.counter.trigramme] for l in likes_raw]
138
+    likes_df = pd.DataFrame(likes_data, columns=likes_cols)
139
+    # About the hashtags
140
+    hashtags_cols = ['keyword']
141
+    hashtags_data = [[h.keyword.text] for h in hashtags_raw]
142
+    hashtags_df = pd.DataFrame(hashtags_data, columns=hashtags_cols)
143
+
144
+
145
+    # Timeline graph
146
+    timeline_resets = resets_df[resets_df.date > (datetime.now() - timedelta(days=1))].copy().reset_index()
147
+    if timeline_resets.shape[0] == 0:
148
+        noTimeline = True
149
+        line_chart = None
150
+    else:
151
+        noTimeline = False
152
+
153
+        # Construct legend for timeline dots
154
+        legend_ = np.zeros(timeline_resets.shape[0], dtype=np.object)
155
+        for i in range(timeline_resets.shape[0]):
156
+            row = timeline_resets.iloc[i]
157
+            if row['self_seum'] == 1:
158
+                legend_[i] = _('%(counter)s: %(reason)s') % {'counter': row['counter_trigram'], 'reason': row['reason']}
159
+            else:
160
+                legend_[i] = _('%(who)s to %(counter)s: %(reason)s') % {'who': row['who_trigram'], 'counter': row['counter_trigram'], 'reason': row['reason']}
161
+        timeline_resets['legend'] = legend_
162
+
163
+        # Generate graph
164
+        resets_ = [['', _('Seum')]]
165
+        for i in range(timeline_resets.shape[0]):
166
+            r = timeline_resets.iloc[i]
167
+            resets_.append([{'v': r.timestamp, 'f': r.formatted_delta}, {'v': 0, 'f': r.legend}])
168
+            # resets_.append({
169
+            #     'timestamp': {'v': r.date.timestamp(), 'f': r.formatted_delta},
170
+            #     'Seum': {'v': 0, 'f': r.legend},
171
+            # })
172
+        line_data = SimpleDataSource(resets_)
173
+        line_chart = gchart.LineChart(line_data, options={
174
+            'lineWidth': 0,
175
+            'pointSize': 10,
176
+            'title': '',
177
+            'vAxis': {'ticks': []},
178
+            'hAxis': {
179
+                'ticks': [
180
+                    {'v': (datetime.now() - timedelta(days=1)
181
+                           ).timestamp(), 'f': _('24h ago')},
182
+                    {'v': datetime.now().timestamp(), 'f': _('Now')}
183
+                ]
184
+            },
185
+            'legend': 'none',
186
+            'height': 90
187
+        })
188
+
189
+    # Graph of greatest seumers
190
+    seum_counts_df = resets_df[['counter_trigram', 'self_seum']].copy()
191
+    seum_counts_df['seum_count'] = np.ones(seum_counts_df.shape[0], dtype=np.float32)
192
+    seum_counts_df = seum_counts_df.groupby(['counter_trigram']).sum().reset_index()
193
+    # TODO: Add the ratio self_seum / seum_count
194
+    if (seum_counts_df.shape[0] == 0):
195
+        noBestSeum = True
196
+        best_chart = None
197
+    else:
198
+        noBestSeum = False
199
+        seum_counts_data = seum_counts_df.sort_values(by='seum_count', ascending=False)[['counter_trigram', 'seum_count']].values.tolist()
200
+        seum_counts_data.insert(0, [_('Trigram'), _('Number of seums')])
201
+        best_data = SimpleDataSource(seum_counts_data[:bestSeumeursNumber])
202
+        best_chart = gchart.ColumnChart(best_data, options={
203
+            'title': '',
204
+            'legend': 'none',
205
+            'vAxis': {'title': _('Number of seums')},
206
+            'hAxis': {'title': _('Trigram')},
207
+        })
208
+
209
+    # Graph of seum activity
210
+    resets_act = resets_df[resets_df.date > (timezone.now() - timedelta(days=365))][['date']].copy()
211
+    resets_act['year'] = resets_df.date.map(lambda d: d.year)
212
+    resets_act['month'] = resets_df.date.map(lambda d: d.month)
213
+    resets_act = resets_act.drop(['date'], axis=1)
214
+    resets_act['month_counts'] = np.ones(resets_act.shape[0], dtype=int)
215
+    resets_act = resets_act.groupby(['year', 'month']).sum().reset_index()
216
+    if resets_act.shape[0] == 0:
217
+        noSeumActivity = True
218
+        activity_chart = None
219
+    else:
220
+        noSeumActivity = False
221
+        seumActivity = [
222
+            [arrow.Arrow(a[0], a[1], 1).format("MMM YYYY", locale=get_language()).capitalize(), a[2]]
223
+            for a in resets_act.values.tolist()
224
+        ]
225
+        seumActivity.insert(0, [_('Month'), _('Number of seums')])
226
+        activity_data = SimpleDataSource(seumActivity)
227
+        activity_chart = gchart.ColumnChart(activity_data, options={
228
+            'title': '',
229
+            'legend': 'none',
230
+            'vAxis': {'title': _('Number of seums')},
231
+            'hAxis': {'title': _('Month')},
232
+        })
233
+
234
+    # Graph of best likers
235
+    best_likers_df = likes_df.drop(['liker', 'counter', 'counter_trigram'], axis=1)
236
+    best_likers_df['count'] = np.ones(best_likers_df.shape[0], dtype=int)
237
+    best_likers_df = best_likers_df.groupby(['liker_trigram']).sum().reset_index()
238
+    if best_likers_df.shape[0] == 0:
239
+        noBestLikers = True
240
+        likers_chart = None
241
+    else:
242
+        noBestLikers = False
243
+        likersCounts = best_likers_df.sort_values(by='count', ascending=False).values.tolist()
244
+        likersCounts.insert(0, [_('Trigram'), _('Number of given likes')])
245
+        likers_data = SimpleDataSource(likersCounts[:bestSeumeursNumber])
246
+        likers_chart = gchart.ColumnChart(likers_data, options={
247
+            'title': '',
248
+            'legend': 'none',
249
+            'vAxis': {'title': _('Number of given likes')},
250
+            'hAxis': {'title': _('Trigram')},
251
+        })
252
+
253
+    # Graph of popular hashtags
254
+    hashtags_df['count'] = np.ones(hashtags_df.shape[0], dtype=int)
255
+    hashtags_df = hashtags_df.groupby(['keyword']).sum().reset_index()
256
+    hashtags_df['keyword'] = hashtags_df.keyword.map(lambda x: '#' + x)
257
+    if hashtags_df.shape[0] == 0:
258
+        noBestHashtags = True
259
+        hashtags_chart = None
260
+    else:
261
+        noBestHashtags = False
262
+        hashtags_data = hashtags_df.sort_values(by='count', ascending=False).values.tolist()
263
+        hashtags_data.insert(0, [_('Hashtag'), _('Number of seums containing the hashtag')])
264
+        hashtags_data = SimpleDataSource(hashtags_data[:bestSeumeursNumber])
265
+        hashtags_chart = gchart.ColumnChart(hashtags_data, options={
266
+            'title': '',
267
+            'legend': 'none',
268
+            'vAxis': {'title': _('Number of seums containing the hashtag')},
269
+            'hAxis': {'title': _('Hashtag')},
270
+        })
271
+
272
+    # Graph of best likee
273
+    best_likees_df = likes_df.drop(['counter', 'liker', 'liker_trigram'], axis=1)
274
+    best_likees_df['count'] = np.ones(best_likees_df.shape[0], dtype=int)
275
+    best_likees_df = best_likees_df.groupby(['counter_trigram']).sum().reset_index()
276
+    if best_likees_df.shape[0] == 0:
277
+        noBestLikees = True
278
+        likees_chart = None
279
+    else:
280
+        noBestLikees = False
281
+        likeesCounts = best_likees_df.sort_values(by='count', ascending=False).values.tolist()
282
+        likeesCounts.insert(0, [_('Trigram'), _('Number of received likes')])
283
+        likees_data = SimpleDataSource(likeesCounts[:bestSeumeursNumber])
284
+        likees_chart = gchart.ColumnChart(likees_data, options={
285
+            'title': '',
286
+            'legend': 'none',
287
+            'vAxis': {'title': _('Number of received likes')},
288
+            'hAxis': {'title': _('Trigram')},
289
+        })
290
+
291
+    # At last we render the page
292
+    return render(request, 'homeTemplate.html', {
293
+        'counters': counters,
294
+        'line_chart': line_chart,
295
+        'best_chart': best_chart,
296
+        'likers_chart': likers_chart,
297
+        'likees_chart': likees_chart,
298
+        'hashtags_chart': hashtags_chart,
299
+        'activity_chart': activity_chart,
300
+        'noTimeline': noTimeline,
301
+        'noBestSeum': noBestSeum,
302
+        'noBestLikers': noBestLikers,
303
+        'noBestLikees': noBestLikees,
304
+        'noBestHashtags': noBestHashtags,
305
+        'noSeumActivity': noSeumActivity,
306
+        'myCounter': myCounter,
307
+    })
308
+
309
+
310
+@login_required
311
+def toggleEmailNotifications(request):
312
+    counter = Counter.objects.get(user=request.user)
313
+    counter.email_notifications = not counter.email_notifications
314
+    counter.save()
315
+    return HttpResponseRedirect(reverse('home'))
316
+
317
+
318
+@login_required
319
+def toggleScoreSorting(request):
320
+    counter = Counter.objects.get(user=request.user)
321
+    counter.sort_by_score = not counter.sort_by_score
322
+    counter.save()
323
+    return HttpResponseRedirect(reverse('home'))
324
+
325
+

+ 15 - 0
counter/views/reset.py

@@ -0,0 +1,15 @@
1
+from django.contrib.auth.decorators import login_required
2
+from django.http import HttpResponseRedirect
3
+
4
+from counter.models import *
5
+
6
+
7
+@login_required
8
+def like(request):
9
+    if (request.method == 'POST'):
10
+        # create a form instance and populate it with data from the request:
11
+        data = dict(request.POST)
12
+        liker = Counter.objects.get(pk=data['liker'][0])
13
+        reset = Reset.objects.get(pk=data['reset'][0])
14
+        like = Like.objects.create(liker=liker, reset=reset)
15
+    return HttpResponseRedirect(data['redirect'][0])

+ 39 - 0
counter/views/user.py

@@ -0,0 +1,39 @@
1
+from django.contrib.auth.models import User
2
+from django.core.urlresolvers import reverse
3
+from django.shortcuts import render
4
+from django.utils.translation import ugettext as _
5
+
6
+from counter.models import Counter
7
+
8
+
9
+def create(request):
10
+    if (request.method == 'POST'):
11
+        # create a form instance and populate it with data from the request:
12
+        data = dict(request.POST)
13
+        email = data['email'][0]
14
+        username = email.split('@')[0]
15
+        trigramme = data['trigramme'][0]
16
+        nick = data['nick'][0]
17
+        password1 = data['password1'][0]
18
+        password2 = data['password2'][0]
19
+        email_notifications = 'email_notifications' in data.keys()
20
+
21
+        if password1 != password2:
22
+            error = _("Passwords do not match.")
23
+            return render(request, 'createUser.html', {'error': error})
24
+
25
+        try:
26
+            test_user = User.objects.get(email=email)
27
+            error = _("A user with this email address already exists.")
28
+            return render(request, 'createUser.html', {'error': error})
29
+        except User.DoesNotExist:
30
+            try:
31
+                user = User.objects.create_user(username, email, password1)
32
+            except IntegrityError:
33
+                error = _("Use another email address, another user has already this login.")
34
+                return render(request, 'createUser.html', {'error': error})
35
+
36
+            counter = Counter.objects.create(name=nick, email=email, trigramme=trigramme, user=user, email_notifications=email_notifications)
37
+            return render(request, 'createUserDone.html', {'login': username})
38
+    else:
39
+        return render(request, 'createUser.html', {'error': None})

+ 583 - 0
locale/en/LC_MESSAGES/django.po

@@ -0,0 +1,583 @@
1
+# SOME DESCRIPTIVE TITLE.
2
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+# This file is distributed under the same license as the PACKAGE package.
4
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+#
6
+#, fuzzy
7
+msgid ""
8
+msgstr ""
9
+"Project-Id-Version: PACKAGE VERSION\n"
10
+"Report-Msgid-Bugs-To: \n"
11
+"POT-Creation-Date: 2017-01-22 14:19+0100\n"
12
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+"Language-Team: LANGUAGE <LL@li.org>\n"
15
+"Language: \n"
16
+"MIME-Version: 1.0\n"
17
+"Content-Type: text/plain; charset=UTF-8\n"
18
+"Content-Transfer-Encoding: 8bit\n"
19
+
20
+#: counter/models.py:12
21
+msgid "name"
22
+msgstr ""
23
+
24
+#: counter/models.py:13
25
+msgid "email"
26
+msgstr ""
27
+
28
+#: counter/models.py:14
29
+msgid "trigram"
30
+msgstr ""
31
+
32
+#: counter/models.py:15
33
+msgid "associated user"
34
+msgstr ""
35
+
36
+#: counter/models.py:16
37
+msgid "email notifications"
38
+msgstr ""
39
+
40
+#: counter/models.py:17
41
+msgid "sort by SeumScore"
42
+msgstr ""
43
+
44
+#: counter/models.py:20
45
+#, python-format
46
+msgid "%(trigram)s (%(name)s)"
47
+msgstr ""
48
+
49
+#: counter/models.py:23
50
+msgid "counter"
51
+msgstr ""
52
+
53
+#: counter/models.py:27 counter/models.py:55
54
+msgid "datetime"
55
+msgstr ""
56
+
57
+#: counter/models.py:28
58
+msgid "reason"
59
+msgstr ""
60
+
61
+#: counter/models.py:29
62
+msgid "victim"
63
+msgstr ""
64
+
65
+#: counter/models.py:30
66
+msgid "seum giver"
67
+msgstr ""
68
+
69
+#: counter/models.py:34
70
+#, python-format
71
+msgid "%(counter)s: %(datetime)s (%(reason)s)"
72
+msgstr ""
73
+
74
+#: counter/models.py:48 counter/models.py:79
75
+msgid "reset"
76
+msgstr ""
77
+
78
+#: counter/models.py:49
79
+msgid "resets"
80
+msgstr ""
81
+
82
+#: counter/models.py:53
83
+msgid "liker"
84
+msgstr ""
85
+
86
+#: counter/models.py:54
87
+msgid "seum"
88
+msgstr ""
89
+
90
+#: counter/models.py:58
91
+msgid "like"
92
+msgstr ""
93
+
94
+#: counter/models.py:59
95
+msgid "likes"
96
+msgstr ""
97
+
98
+#: counter/models.py:63
99
+#, python-format
100
+msgid "%(liker)s likes %(reset)s"
101
+msgstr ""
102
+
103
+#: counter/models.py:70
104
+msgid "keyword"
105
+msgstr ""
106
+
107
+#: counter/models.py:71
108
+msgid "keywords"
109
+msgstr ""
110
+
111
+#: counter/models.py:78 counter/models.py:82
112
+msgid "hashtag"
113
+msgstr ""
114
+
115
+#: counter/models.py:83
116
+msgid "hashtags"
117
+msgstr ""
118
+
119
+#: counter/models.py:86
120
+#, python-format
121
+msgid "%(keyword)s for %(who)s"
122
+msgstr ""
123
+
124
+#: counter/templates/counterTemplate.html:47
125
+#: counter/templates/homeTemplate.html:32
126
+#: counter/templates/homeTemplate.html:134
127
+msgid "Has not got the seum yet"
128
+msgstr ""
129
+
130
+#: counter/templates/counterTemplate.html:51
131
+#: counter/templates/homeTemplate.html:138
132
+msgid "Has got the seum"
133
+msgstr ""
134
+
135
+#: counter/templates/counterTemplate.html:53
136
+#: counter/templates/homeTemplate.html:140
137
+msgid "threw him/her the seum"
138
+msgstr ""
139
+
140
+#: counter/templates/counterTemplate.html:61
141
+#: counter/templates/homeTemplate.html:45
142
+msgid "Reset"
143
+msgstr ""
144
+
145
+#: counter/templates/counterTemplate.html:67
146
+msgid "Motive for the seum:"
147
+msgstr ""
148
+
149
+#: counter/templates/counterTemplate.html:74
150
+#: counter/templates/homeTemplate.html:89
151
+msgid "Throw the seum"
152
+msgstr ""
153
+
154
+#: counter/templates/counterTemplate.html:83
155
+msgid "Timeline of the seum"
156
+msgstr ""
157
+
158
+#: counter/templates/counterTemplate.html:88
159
+msgid "No timeline of the seum yet..."
160
+msgstr ""
161
+
162
+#: counter/templates/counterTemplate.html:102
163
+msgid "Seum history"
164
+msgstr ""
165
+
166
+#: counter/templates/counterTemplate.html:110
167
+#: counter/templates/hashtagTemplate.html:26
168
+msgid "Date"
169
+msgstr ""
170
+
171
+#: counter/templates/counterTemplate.html:111
172
+#: counter/templates/hashtagTemplate.html:27
173
+#: counter/templates/homeTemplate.html:82
174
+msgid "Motive"
175
+msgstr ""
176
+
177
+#: counter/templates/counterTemplate.html:112
178
+#: counter/templates/hashtagTemplate.html:29
179
+msgid "Seum thrower"
180
+msgstr ""
181
+
182
+#: counter/templates/counterTemplate.html:113
183
+#: counter/templates/hashtagTemplate.html:30
184
+msgid "Number of likes"
185
+msgstr ""
186
+
187
+#: counter/templates/counterTemplate.html:138
188
+#: counter/templates/hashtagTemplate.html:56
189
+msgid "Back to counters list"
190
+msgstr ""
191
+
192
+#: counter/templates/createUser.html:17
193
+msgid "Create your seum counter!"
194
+msgstr ""
195
+
196
+#: counter/templates/createUser.html:22
197
+msgid ""
198
+"The email address will be used for password reinitialisation. Your login on "
199
+"this website will be the first part of this address (before the @)."
200
+msgstr ""
201
+
202
+#: counter/templates/createUser.html:24
203
+msgid "Email address"
204
+msgstr ""
205
+
206
+#: counter/templates/createUser.html:27
207
+msgid ""
208
+"If you check the box below, you will receive an email from <tt>seum@merigoux."
209
+"ovh</tt> each time someone get the seum on the site. Spamming but so "
210
+"enjoyable, can be deactivated and reactivated later."
211
+msgstr ""
212
+
213
+#: counter/templates/createUser.html:31
214
+msgid "Email notifications"
215
+msgstr ""
216
+
217
+#: counter/templates/createUser.html:34
218
+msgid ""
219
+"Other users will see your nickname and your trigram only, it will be your "
220
+"seum identity!"
221
+msgstr ""
222
+
223
+#: counter/templates/createUser.html:36 counter/templates/homeTemplate.html:76
224
+#: counter/views/home.py:203 counter/views/home.py:209
225
+#: counter/views/home.py:247 counter/views/home.py:253
226
+#: counter/views/home.py:285 counter/views/home.py:291
227
+msgid "Trigram"
228
+msgstr ""
229
+
230
+#: counter/templates/createUser.html:40
231
+msgid "Nick"
232
+msgstr ""
233
+
234
+#: counter/templates/createUser.html:43
235
+msgid ""
236
+"I could have required 10 characters with one digit, an emoji, three "
237
+"uppercase letters and two lowercase ones to throw you the seum, but actually "
238
+"you can choose whatever you want."
239
+msgstr ""
240
+
241
+#: counter/templates/createUser.html:45 counter/templates/login.html:27
242
+msgid "Password"
243
+msgstr ""
244
+
245
+#: counter/templates/createUser.html:49
246
+msgid "Confirm password"
247
+msgstr ""
248
+
249
+#: counter/templates/createUser.html:52
250
+msgid ""
251
+"If this form has given you the seum, do not forget to reset your counter "
252
+"once you are logged in!"
253
+msgstr ""
254
+
255
+#: counter/templates/createUser.html:54
256
+msgid "Create the counter"
257
+msgstr ""
258
+
259
+#: counter/templates/createUser.html:64 counter/templates/login.html:43
260
+#: counter/templates/passwordChange.html:45
261
+#: counter/templates/passwordReset.html:37
262
+#: counter/templates/passwordResetConfirm.html:46
263
+#: counter/templates/passwordResetConfirm.html:57
264
+msgid "Error"
265
+msgstr ""
266
+
267
+#: counter/templates/createUserDone.html:5
268
+msgid "Password successfully changed!"
269
+msgstr ""
270
+
271
+#: counter/templates/createUserDone.html:17
272
+#: counter/templates/passwordChangeDone.html:17
273
+#: counter/templates/passwordResetComplete.html:17
274
+#: counter/templates/passwordResetDone.html:17
275
+msgid "Victory!"
276
+msgstr ""
277
+
278
+#: counter/templates/createUserDone.html:20
279
+msgid "You have created your counter! Your login is "
280
+msgstr ""
281
+
282
+#: counter/templates/createUserDone.html:22
283
+msgid "Login to access the website!"
284
+msgstr ""
285
+
286
+#: counter/templates/hashtagTemplate.html:20
287
+msgid "Seums containing"
288
+msgstr ""
289
+
290
+#: counter/templates/hashtagTemplate.html:28
291
+msgid "Victim"
292
+msgstr ""
293
+
294
+#: counter/templates/homeTemplate.html:6
295
+msgid "Counters"
296
+msgstr ""
297
+
298
+#: counter/templates/homeTemplate.html:36
299
+msgid "I got the seum"
300
+msgstr ""
301
+
302
+#: counter/templates/homeTemplate.html:52
303
+msgid "Motive of the seum"
304
+msgstr ""
305
+
306
+#: counter/templates/homeTemplate.html:59
307
+msgid "I've got the seum"
308
+msgstr ""
309
+
310
+#: counter/templates/homeTemplate.html:70
311
+msgid "Break the seum wall"
312
+msgstr ""
313
+
314
+#: counter/templates/homeTemplate.html:155
315
+msgid "Timeline of the 24h of the seum"
316
+msgstr ""
317
+
318
+#: counter/templates/homeTemplate.html:160
319
+msgid "No seum in the last past 24h..."
320
+msgstr ""
321
+
322
+#: counter/templates/homeTemplate.html:173
323
+msgid "Best seumers"
324
+msgstr ""
325
+
326
+#: counter/templates/homeTemplate.html:178
327
+#: counter/templates/homeTemplate.html:250
328
+msgid "Nobody has got the seum..."
329
+msgstr ""
330
+
331
+#: counter/templates/homeTemplate.html:191
332
+msgid "Most liked seumers"
333
+msgstr ""
334
+
335
+#: counter/templates/homeTemplate.html:196
336
+#: counter/templates/homeTemplate.html:232
337
+msgid "Nobody liked..."
338
+msgstr ""
339
+
340
+#: counter/templates/homeTemplate.html:209
341
+msgid "Most popular hashtags"
342
+msgstr ""
343
+
344
+#: counter/templates/homeTemplate.html:214
345
+msgid "Nobody used any hashtag..."
346
+msgstr ""
347
+
348
+#: counter/templates/homeTemplate.html:227
349
+msgid "Best likers of seum"
350
+msgstr ""
351
+
352
+#: counter/templates/homeTemplate.html:245
353
+msgid "Seum activity"
354
+msgstr ""
355
+
356
+#: counter/templates/homeTemplate.html:261
357
+msgid "Logout"
358
+msgstr ""
359
+
360
+#: counter/templates/homeTemplate.html:262
361
+#: counter/templates/passwordChange.html:5
362
+#: counter/templates/passwordChange.html:35
363
+#: counter/templates/passwordResetConfirm.html:5
364
+#: counter/templates/passwordResetConfirm.html:36
365
+msgid "Change password"
366
+msgstr ""
367
+
368
+#: counter/templates/homeTemplate.html:265
369
+msgid "Deactivate email notifications"
370
+msgstr ""
371
+
372
+#: counter/templates/homeTemplate.html:267
373
+msgid "Activate email notifications"
374
+msgstr ""
375
+
376
+#: counter/templates/homeTemplate.html:272
377
+msgid "Sort seums by date"
378
+msgstr ""
379
+
380
+#: counter/templates/homeTemplate.html:274
381
+msgid "Sort seums by score"
382
+msgstr ""
383
+
384
+#: counter/templates/login.html:5
385
+msgid "Login"
386
+msgstr ""
387
+
388
+#: counter/templates/login.html:17
389
+msgid "Login to get the seum!"
390
+msgstr ""
391
+
392
+#: counter/templates/login.html:23
393
+msgid "Username"
394
+msgstr ""
395
+
396
+#: counter/templates/login.html:32
397
+msgid "Log in"
398
+msgstr ""
399
+
400
+#: counter/templates/login.html:33 counter/templates/passwordReset.html:5
401
+msgid "Password forgotten"
402
+msgstr ""
403
+
404
+#: counter/templates/login.html:46 counter/templates/passwordReset.html:40
405
+msgid "You cannot even login... seum!"
406
+msgstr ""
407
+
408
+#: counter/templates/login.html:54
409
+msgid "You don't have a counter yet?"
410
+msgstr ""
411
+
412
+#: counter/templates/login.html:57
413
+msgid "Hurry up and create it to share your seum!"
414
+msgstr ""
415
+
416
+#: counter/templates/login.html:59
417
+msgid "Create a counter"
418
+msgstr ""
419
+
420
+#: counter/templates/passwordChange.html:17
421
+#: counter/templates/passwordResetConfirm.html:18
422
+msgid "Change your password of the seum!"
423
+msgstr ""
424
+
425
+#: counter/templates/passwordChange.html:23
426
+#: counter/templates/passwordResetConfirm.html:24
427
+msgid "Old password"
428
+msgstr ""
429
+
430
+#: counter/templates/passwordChange.html:27
431
+#: counter/templates/passwordResetConfirm.html:28
432
+msgid "New password"
433
+msgstr ""
434
+
435
+#: counter/templates/passwordChange.html:31
436
+#: counter/templates/passwordResetConfirm.html:32
437
+msgid "Confirm new password"
438
+msgstr ""
439
+
440
+#: counter/templates/passwordChange.html:48
441
+#: counter/templates/passwordResetConfirm.html:49
442
+msgid "You don't even manage to change your password, seum."
443
+msgstr ""
444
+
445
+#: counter/templates/passwordChangeDone.html:5
446
+msgid "Password changed!"
447
+msgstr ""
448
+
449
+#: counter/templates/passwordChangeDone.html:20
450
+msgid "You changed your password!"
451
+msgstr ""
452
+
453
+#: counter/templates/passwordChangeDone.html:22
454
+#: counter/templates/passwordResetComplete.html:22
455
+msgid "Home"
456
+msgstr ""
457
+
458
+#: counter/templates/passwordReset.html:17
459
+msgid "Reset password"
460
+msgstr ""
461
+
462
+#: counter/templates/passwordReset.html:23
463
+msgid "Email for the reinitialisation link"
464
+msgstr ""
465
+
466
+#: counter/templates/passwordReset.html:27
467
+msgid "Send"
468
+msgstr ""
469
+
470
+#: counter/templates/passwordResetComplete.html:5
471
+msgid "Your password has been reset"
472
+msgstr ""
473
+
474
+#: counter/templates/passwordResetComplete.html:20
475
+msgid ""
476
+"That's it! You have reset your password! You can reset your counter now."
477
+msgstr ""
478
+
479
+#: counter/templates/passwordResetConfirm.html:60
480
+msgid "You've got the seum, the link that we sent you has already been used."
481
+msgstr ""
482
+
483
+#: counter/templates/passwordResetDone.html:5
484
+msgid "Email sent!"
485
+msgstr ""
486
+
487
+#: counter/templates/passwordResetDone.html:20
488
+msgid ""
489
+"We have sent you an email. Follow the instructions to reinitialise your "
490
+"password."
491
+msgstr ""
492
+
493
+#: counter/templates/resetEmail.txt:3
494
+msgid ""
495
+"You have lost your password and you've got the seum. You have to follow this "
496
+"link (or copy-paste it in your browser) to reinitialise it:"
497
+msgstr ""
498
+
499
+#: counter/templates/resetEmail.txt:7
500
+msgid "Your login is {{ user.username }} in case you have forgotten it too."
501
+msgstr ""
502
+
503
+#: counter/templates/resetEmail.txt:18
504
+msgid ""
505
+"P.S.: If you don't want to receive these mails anymore, contact us at denis."
506
+"merigoux@gmail.com"
507
+msgstr ""
508
+
509
+#: counter/views/counter.py:41
510
+msgid "unknown"
511
+msgstr ""
512
+
513
+#: counter/views/counter.py:81
514
+#, python-format
515
+msgid "From %(who)s: %(reason)s"
516
+msgstr ""
517
+
518
+#: counter/views/home.py:161
519
+#, python-format
520
+msgid "%(counter)s: %(reason)s"
521
+msgstr ""
522
+
523
+#: counter/views/home.py:163
524
+#, python-format
525
+msgid "%(who)s to %(counter)s: %(reason)s"
526
+msgstr ""
527
+
528
+#: counter/views/home.py:167
529
+msgid "Seum"
530
+msgstr ""
531
+
532
+#: counter/views/home.py:184
533
+msgid "24h ago"
534
+msgstr ""
535
+
536
+#: counter/views/home.py:185
537
+msgid "Now"
538
+msgstr ""
539
+
540
+#: counter/views/home.py:203 counter/views/home.py:208
541
+#: counter/views/home.py:228 counter/views/home.py:233
542
+msgid "Number of seums"
543
+msgstr ""
544
+
545
+#: counter/views/home.py:228 counter/views/home.py:234
546
+msgid "Month"
547
+msgstr ""
548
+
549
+#: counter/views/home.py:247 counter/views/home.py:252
550
+msgid "Number of given likes"
551
+msgstr ""
552
+
553
+#: counter/views/home.py:266 counter/views/home.py:272
554
+msgid "Hashtag"
555
+msgstr ""
556
+
557
+#: counter/views/home.py:266 counter/views/home.py:271
558
+msgid "Number of seums containing the hashtag"
559
+msgstr ""
560
+
561
+#: counter/views/home.py:285 counter/views/home.py:290
562
+msgid "Number of received likes"
563
+msgstr ""
564
+
565
+#: counter/views/user.py:22
566
+msgid "Passwords do not match."
567
+msgstr ""
568
+
569
+#: counter/views/user.py:27
570
+msgid "A user with this email address already exists."
571
+msgstr ""
572
+
573
+#: counter/views/user.py:33
574
+msgid "Use another email address, another user has already this login."
575
+msgstr ""
576
+
577
+#: seum/settings.py:111
578
+msgid "English"
579
+msgstr ""
580
+
581
+#: seum/settings.py:112
582
+msgid "French"
583
+msgstr ""

+ 604 - 0
locale/fr/LC_MESSAGES/django.po

@@ -0,0 +1,604 @@
1
+# SOME DESCRIPTIVE TITLE.
2
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+# This file is distributed under the same license as the PACKAGE package.
4
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+#
6
+#, fuzzy
7
+msgid ""
8
+msgstr ""
9
+"Project-Id-Version: PACKAGE VERSION\n"
10
+"Report-Msgid-Bugs-To: \n"
11
+"POT-Creation-Date: 2017-01-22 14:19+0100\n"
12
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+"Language-Team: LANGUAGE <LL@li.org>\n"
15
+"Language: \n"
16
+"MIME-Version: 1.0\n"
17
+"Content-Type: text/plain; charset=UTF-8\n"
18
+"Content-Transfer-Encoding: 8bit\n"
19
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
20
+
21
+#: counter/models.py:12
22
+msgid "name"
23
+msgstr "nom"
24
+
25
+#: counter/models.py:13
26
+msgid "email"
27
+msgstr "email"
28
+
29
+#: counter/models.py:14
30
+msgid "trigram"
31
+msgstr "trigramme"
32
+
33
+#: counter/models.py:15
34
+msgid "associated user"
35
+msgstr "utilisateur associé"
36
+
37
+#: counter/models.py:16
38
+msgid "email notifications"
39
+msgstr "notifications par email"
40
+
41
+#: counter/models.py:17
42
+msgid "sort by SeumScore"
43
+msgstr "trier par SeumScore"
44
+
45
+#: counter/models.py:20
46
+#, python-format
47
+msgid "%(trigram)s (%(name)s)"
48
+msgstr "%(trigram)s (%(name)s)"
49
+
50
+#: counter/models.py:23
51
+msgid "counter"
52
+msgstr "compteur"
53
+
54
+#: counter/models.py:27 counter/models.py:55
55
+msgid "datetime"
56
+msgstr "date et heure"
57
+
58
+#: counter/models.py:28
59
+msgid "reason"
60
+msgstr "raison"
61
+
62
+#: counter/models.py:29
63
+msgid "victim"
64
+msgstr "victime"
65
+
66
+#: counter/models.py:30
67
+msgid "seum giver"
68
+msgstr "fouteur de seum"
69
+
70
+#: counter/models.py:34
71
+#, python-format
72
+msgid "%(counter)s: %(datetime)s (%(reason)s)"
73
+msgstr "%(counter)s : %(datetime)s (%(reason)s)"
74
+
75
+#: counter/models.py:48 counter/models.py:79
76
+msgid "reset"
77
+msgstr "remise à zéro"
78
+
79
+#: counter/models.py:49
80
+msgid "resets"
81
+msgstr "remises à zéro"
82
+
83
+#: counter/models.py:53
84
+msgid "liker"
85
+msgstr "likeur"
86
+
87
+#: counter/models.py:54
88
+msgid "seum"
89
+msgstr "seum"
90
+
91
+#: counter/models.py:58
92
+msgid "like"
93
+msgstr "like"
94
+
95
+#: counter/models.py:59
96
+msgid "likes"
97
+msgstr "likes"
98
+
99
+#: counter/models.py:63
100
+#, python-format
101
+msgid "%(liker)s likes %(reset)s"
102
+msgstr "%(liker)s aime %(reset)s"
103
+
104
+#: counter/models.py:70
105
+msgid "keyword"
106
+msgstr "mot-clé"
107
+
108
+#: counter/models.py:71
109
+msgid "keywords"
110
+msgstr "mots-clés"
111
+
112
+#: counter/models.py:78 counter/models.py:82
113
+msgid "hashtag"
114
+msgstr "hashtag"
115
+
116
+#: counter/models.py:83
117
+msgid "hashtags"
118
+msgstr "hashtags"
119
+
120
+#: counter/models.py:86
121
+#, python-format
122
+msgid "%(keyword)s for %(who)s"
123
+msgstr "%(keyword)s pour %(who)s"
124
+
125
+#: counter/templates/counterTemplate.html:47
126
+#: counter/templates/homeTemplate.html:32
127
+#: counter/templates/homeTemplate.html:134
128
+msgid "Has not got the seum yet"
129
+msgstr "N'a pas encore eu le seum"
130
+
131
+#: counter/templates/counterTemplate.html:51
132
+#: counter/templates/homeTemplate.html:138
133
+msgid "Has got the seum"
134
+msgstr "A eu le seum"
135
+
136
+#: counter/templates/counterTemplate.html:53
137
+#: counter/templates/homeTemplate.html:140
138
+msgid "threw him/her the seum"
139
+msgstr "lui a foutu le seum"
140
+
141
+#: counter/templates/counterTemplate.html:61
142
+#: counter/templates/homeTemplate.html:45
143
+msgid "Reset"
144
+msgstr "Remise à zéro"
145
+
146
+#: counter/templates/counterTemplate.html:67
147
+msgid "Motive for the seum:"
148
+msgstr "Motif du seum :"
149
+
150
+#: counter/templates/counterTemplate.html:74
151
+#: counter/templates/homeTemplate.html:89
152
+msgid "Throw the seum"
153
+msgstr "Foutre le seum"
154
+
155
+#: counter/templates/counterTemplate.html:83
156
+msgid "Timeline of the seum"
157
+msgstr "Timeline du seum"
158
+
159
+#: counter/templates/counterTemplate.html:88
160
+msgid "No timeline of the seum yet..."
161
+msgstr "Pas encore de timeline du seum..."
162
+
163
+#: counter/templates/counterTemplate.html:102
164
+msgid "Seum history"
165
+msgstr "Historique du seum"
166
+
167
+#: counter/templates/counterTemplate.html:110
168
+#: counter/templates/hashtagTemplate.html:26
169
+msgid "Date"
170
+msgstr "Date"
171
+
172
+#: counter/templates/counterTemplate.html:111
173
+#: counter/templates/hashtagTemplate.html:27
174
+#: counter/templates/homeTemplate.html:82
175
+msgid "Motive"
176
+msgstr "Motif"
177
+
178
+#: counter/templates/counterTemplate.html:112
179
+#: counter/templates/hashtagTemplate.html:29
180
+msgid "Seum thrower"
181
+msgstr "Fouteur de seum"
182
+
183
+#: counter/templates/counterTemplate.html:113
184
+#: counter/templates/hashtagTemplate.html:30
185
+msgid "Number of likes"
186
+msgstr "Nombre de likes"
187
+
188
+#: counter/templates/counterTemplate.html:138
189
+#: counter/templates/hashtagTemplate.html:56
190
+msgid "Back to counters list"
191
+msgstr "Retour à la liste des compteurs"
192
+
193
+#: counter/templates/createUser.html:17
194
+msgid "Create your seum counter!"
195
+msgstr "Crée ton compteur de seum !"
196
+
197
+#: counter/templates/createUser.html:22
198
+msgid ""
199
+"The email address will be used for password reinitialisation. Your login on "
200
+"this website will be the first part of this address (before the @)."
201
+msgstr ""
202
+"L'adresse e-mail sera utilisée pour la réinitialisation de ton mot de passe. "
203
+"Ton login sur le site sera la première partie de cette adresse (avant le @)."
204
+
205
+#: counter/templates/createUser.html:24
206
+msgid "Email address"
207
+msgstr "Adresse email"
208
+
209
+#: counter/templates/createUser.html:27
210
+msgid ""
211
+"If you check the box below, you will receive an email from <tt>seum@merigoux."
212
+"ovh</tt> each time someone get the seum on the site. Spamming but so "
213
+"enjoyable, can be deactivated and reactivated later."
214
+msgstr ""
215
+"Si tu coches la case en dessous, tu recevras un mail de <tt>seum@merigoux."
216
+"ovh</tt> à chaque fois que quelqu'un aura le seum sur le site. Spammatoire "
217
+"mais jouissif, peut-être désactivé ou réactivé par la suite."
218
+
219
+#: counter/templates/createUser.html:31
220
+msgid "Email notifications"
221
+msgstr "Notifications par email"
222
+
223
+#: counter/templates/createUser.html:34
224
+msgid ""
225
+"Other users will see your nickname and your trigram only, it will be your "
226
+"seum identity!"
227
+msgstr ""
228
+"Les autres utilisateurs ne pourront voir que ton pseudo et ton trigramme, ce "
229
+"sera ton identité seumesque !"
230
+
231
+#: counter/templates/createUser.html:36 counter/templates/homeTemplate.html:76
232
+#: counter/views/home.py:203 counter/views/home.py:209
233
+#: counter/views/home.py:247 counter/views/home.py:253
234
+#: counter/views/home.py:285 counter/views/home.py:291
235
+msgid "Trigram"
236
+msgstr "Trigramme"
237
+
238
+#: counter/templates/createUser.html:40
239
+msgid "Nick"
240
+msgstr "Pseudo"
241
+
242
+#: counter/templates/createUser.html:43
243
+msgid ""
244
+"I could have required 10 characters with one digit, an emoji, three "
245
+"uppercase letters and two lowercase ones to throw you the seum, but actually "
246
+"you can choose whatever you want."
247
+msgstr ""
248
+"J'aurais pu exiger 10 caractères dont un chiffre, un emoji, 3 majuscules et "
249
+"2 minuscules pour te foutre le seum mais en fait tu peux mettre ce que tu "
250
+"veux."
251
+
252
+#: counter/templates/createUser.html:45 counter/templates/login.html:27
253
+msgid "Password"
254
+msgstr "Mot de passe"
255
+
256
+#: counter/templates/createUser.html:49
257
+msgid "Confirm password"
258
+msgstr "Confirmer le mot de passe"
259
+
260
+#: counter/templates/createUser.html:52
261
+msgid ""
262
+"If this form has given you the seum, do not forget to reset your counter "
263
+"once you are logged in!"
264
+msgstr ""
265
+"Si ce formulaire t'as foutu le seum, n'oublie pas de remettre ton compteur à "
266
+"zéro en arrivant sur le site."
267
+
268
+#: counter/templates/createUser.html:54
269
+msgid "Create the counter"
270
+msgstr "Créer le compteur"
271
+
272
+#: counter/templates/createUser.html:64 counter/templates/login.html:43
273
+#: counter/templates/passwordChange.html:45
274
+#: counter/templates/passwordReset.html:37
275
+#: counter/templates/passwordResetConfirm.html:46
276
+#: counter/templates/passwordResetConfirm.html:57
277
+msgid "Error"
278
+msgstr "Erreur"
279
+
280
+#: counter/templates/createUserDone.html:5
281
+msgid "Password successfully changed!"
282
+msgstr "Mot de passe changé !"
283
+
284
+#: counter/templates/createUserDone.html:17
285
+#: counter/templates/passwordChangeDone.html:17
286
+#: counter/templates/passwordResetComplete.html:17
287
+#: counter/templates/passwordResetDone.html:17
288
+msgid "Victory!"
289
+msgstr "Victoire !"
290
+
291
+#: counter/templates/createUserDone.html:20
292
+msgid "You have created your counter! Your login is "
293
+msgstr "Tu as créé ton compteur ! Ton login est "
294
+
295
+#: counter/templates/createUserDone.html:22
296
+msgid "Login to access the website!"
297
+msgstr "Connecte-toi pour accéder au site !"
298
+
299
+#: counter/templates/hashtagTemplate.html:20
300
+msgid "Seums containing"
301
+msgstr "Liste des seums contenant"
302
+
303
+#: counter/templates/hashtagTemplate.html:28
304
+msgid "Victim"
305
+msgstr "Victime"
306
+
307
+#: counter/templates/homeTemplate.html:6
308
+msgid "Counters"
309
+msgstr "Compteurs"
310
+
311
+#: counter/templates/homeTemplate.html:36
312
+msgid "I got the seum"
313
+msgstr "J'ai eu le seum"
314
+
315
+#: counter/templates/homeTemplate.html:52
316
+msgid "Motive of the seum"
317
+msgstr "Motif du seum"
318
+
319
+#: counter/templates/homeTemplate.html:59
320
+msgid "I've got the seum"
321
+msgstr "J'ai le seum"
322
+
323
+#: counter/templates/homeTemplate.html:70
324
+msgid "Break the seum wall"
325
+msgstr "Brise le mur du seum"
326
+
327
+#: counter/templates/homeTemplate.html:155
328
+msgid "Timeline of the 24h of the seum"
329
+msgstr "Timeline des 24 heures du seum"
330
+
331
+#: counter/templates/homeTemplate.html:160
332
+msgid "No seum in the last past 24h..."
333
+msgstr "Pas de seum durant les dernières 24h..."
334
+
335
+#: counter/templates/homeTemplate.html:173
336
+msgid "Best seumers"
337
+msgstr "Meilleurs seumers"
338
+
339
+#: counter/templates/homeTemplate.html:178
340
+#: counter/templates/homeTemplate.html:250
341
+msgid "Nobody has got the seum..."
342
+msgstr "Personne n'a eu le seum..."
343
+
344
+#: counter/templates/homeTemplate.html:191
345
+msgid "Most liked seumers"
346
+msgstr "Seumers les plus likés"
347
+
348
+#: counter/templates/homeTemplate.html:196
349
+#: counter/templates/homeTemplate.html:232
350
+msgid "Nobody liked..."
351
+msgstr "Personne n'a aimé..."
352
+
353
+#: counter/templates/homeTemplate.html:209
354
+msgid "Most popular hashtags"
355
+msgstr "Hashtags les plus populaires"
356
+
357
+#: counter/templates/homeTemplate.html:214
358
+msgid "Nobody used any hashtag..."
359
+msgstr "Personne n'a utilisé de hashtag..."
360
+
361
+#: counter/templates/homeTemplate.html:227
362
+msgid "Best likers of seum"
363
+msgstr "Meilleurs likeurs de seum"
364
+
365
+#: counter/templates/homeTemplate.html:245
366
+msgid "Seum activity"
367
+msgstr "Activité seumesque"
368
+
369
+#: counter/templates/homeTemplate.html:261
370
+msgid "Logout"
371
+msgstr "Se déconnecter"
372
+
373
+#: counter/templates/homeTemplate.html:262
374
+#: counter/templates/passwordChange.html:5
375
+#: counter/templates/passwordChange.html:35
376
+#: counter/templates/passwordResetConfirm.html:5
377
+#: counter/templates/passwordResetConfirm.html:36
378
+msgid "Change password"
379
+msgstr "Modifier le mot de passe"
380
+
381
+#: counter/templates/homeTemplate.html:265
382
+msgid "Deactivate email notifications"
383
+msgstr "Désactiver les notifications par email"
384
+
385
+#: counter/templates/homeTemplate.html:267
386
+msgid "Activate email notifications"
387
+msgstr "Activer les notifications par email"
388
+
389
+#: counter/templates/homeTemplate.html:272
390
+msgid "Sort seums by date"
391
+msgstr "Trier les seums par ancienneté"
392
+
393
+#: counter/templates/homeTemplate.html:274
394
+msgid "Sort seums by score"
395
+msgstr "Trier les seums par score"
396
+
397
+#: counter/templates/login.html:5
398
+msgid "Login"
399
+msgstr "Login"
400
+
401
+#: counter/templates/login.html:17
402
+msgid "Login to get the seum!"
403
+msgstr "Connecte-toi pour avoir le seum !"
404
+
405
+#: counter/templates/login.html:23
406
+msgid "Username"
407
+msgstr "Nom d'utilisateur"
408
+
409
+#: counter/templates/login.html:32
410
+msgid "Log in"
411
+msgstr "Se connecter"
412
+
413
+#: counter/templates/login.html:33 counter/templates/passwordReset.html:5
414
+msgid "Password forgotten"
415
+msgstr "Mot de passe oublié"
416
+
417
+#: counter/templates/login.html:46 counter/templates/passwordReset.html:40
418
+msgid "You cannot even login... seum!"
419
+msgstr "Tu n'arrives mêmes pas à te connecter, seum !"
420
+
421
+#: counter/templates/login.html:54
422
+msgid "You don't have a counter yet?"
423
+msgstr "Pas encore de compteur ?"
424
+
425
+#: counter/templates/login.html:57
426
+msgid "Hurry up and create it to share your seum!"
427
+msgstr "Dépêche-toi de le créer pour partager ton seum !"
428
+
429
+#: counter/templates/login.html:59
430
+msgid "Create a counter"
431
+msgstr "Créer un compteur"
432
+
433
+#: counter/templates/passwordChange.html:17
434
+#: counter/templates/passwordResetConfirm.html:18
435
+msgid "Change your password of the seum!"
436
+msgstr "Change ton mot de passe du seum !"
437
+
438
+#: counter/templates/passwordChange.html:23
439
+#: counter/templates/passwordResetConfirm.html:24
440
+msgid "Old password"
441
+msgstr "Ancien mot de passe"
442
+
443
+#: counter/templates/passwordChange.html:27
444
+#: counter/templates/passwordResetConfirm.html:28
445
+msgid "New password"
446
+msgstr "Nouveau mot de passe"
447
+
448
+#: counter/templates/passwordChange.html:31
449
+#: counter/templates/passwordResetConfirm.html:32
450
+msgid "Confirm new password"
451
+msgstr "Confirmer le nouveau mot de passe"
452
+
453
+#: counter/templates/passwordChange.html:48
454
+#: counter/templates/passwordResetConfirm.html:49
455
+msgid "You don't even manage to change your password, seum."
456
+msgstr "T'arrives même pas à changer ton mot de passe, seum."
457
+
458
+#: counter/templates/passwordChangeDone.html:5
459
+msgid "Password changed!"
460
+msgstr "Mot de passe changé !"
461
+
462
+#: counter/templates/passwordChangeDone.html:20
463
+msgid "You changed your password!"
464
+msgstr "Tu as changé ton mot de passe !"
465
+
466
+#: counter/templates/passwordChangeDone.html:22
467
+#: counter/templates/passwordResetComplete.html:22
468
+msgid "Home"
469
+msgstr "Accueil"
470
+
471
+#: counter/templates/passwordReset.html:17
472
+msgid "Reset password"
473
+msgstr "Réinitialiser le mot de passe"
474
+
475
+#: counter/templates/passwordReset.html:23
476
+msgid "Email for the reinitialisation link"
477
+msgstr "Email auquel sera envoyé le lien de réinitialisation"
478
+
479
+#: counter/templates/passwordReset.html:27
480
+msgid "Send"
481
+msgstr "Envoyer"
482
+
483
+#: counter/templates/passwordResetComplete.html:5
484
+msgid "Your password has been reset"
485
+msgstr "Mot de passe changé !"
486
+
487
+#: counter/templates/passwordResetComplete.html:20
488
+msgid ""
489
+"That's it! You have reset your password! You can reset your counter now."
490
+msgstr ""
491
+"Ça y est tu as réinitialisé ton mot de passe ! Du coup tu peux aller "
492
+"remettre ton compteur à zéro."
493
+
494
+#: counter/templates/passwordResetConfirm.html:60
495
+msgid "You've got the seum, the link that we sent you has already been used."
496
+msgstr ""
497
+"T'as le seum, le lien qui t'as été donné pour visiter cette page est déjà "
498
+"utilisé."
499
+
500
+#: counter/templates/passwordResetDone.html:5
501
+msgid "Email sent!"
502
+msgstr "Email envoyé !"
503
+
504
+#: counter/templates/passwordResetDone.html:20
505
+msgid ""
506
+"We have sent you an email. Follow the instructions to reinitialise your "
507
+"password."
508
+msgstr ""
509
+"Un mail t'a été envoyé, suis les instructions pour aller réinitialiser ton "
510
+"mot de passe."
511
+
512
+#: counter/templates/resetEmail.txt:3
513
+msgid ""
514
+"You have lost your password and you've got the seum. You have to follow this "
515
+"link (or copy-paste it in your browser) to reinitialise it:"
516
+msgstr ""
517
+"T'as perdu ton mot de passe et t'as le seum. "
518
+"Il te reste plus qu'à suivre le lien pour le réinitialiser :"
519
+
520
+#: counter/templates/resetEmail.txt:7
521
+msgid "Your login is {{ user.username }} in case you have forgotten it too."
522
+msgstr "Ton nom d'utilisateur c'est {{ user.username }} au cas où tu l'aurais oublié aussi."
523
+
524
+#: counter/templates/resetEmail.txt:18
525
+msgid ""
526
+"P.S.: If you don't want to receive these mails anymore, contact us at denis."
527
+"merigoux@gmail.com"
528
+msgstr "P.S. : Pour ne plus recevoir ces messages, envoie un mail à denis.merigoux@gmail.com"
529
+
530
+#: counter/views/counter.py:41
531
+msgid "unknown"
532
+msgstr "inconnu"
533
+
534
+#: counter/views/counter.py:81
535
+#, python-format
536
+msgid "From %(who)s: %(reason)s"
537
+msgstr "De %(who)s : %(reason)s"
538
+
539
+#: counter/views/home.py:161
540
+#, python-format
541
+msgid "%(counter)s: %(reason)s"
542
+msgstr "%(counter)s : %(reason)s"
543
+
544
+#: counter/views/home.py:163
545
+#, python-format
546
+msgid "%(who)s to %(counter)s: %(reason)s"
547
+msgstr "%(who)s pour %(counter)s : %(reason)s"
548
+
549
+#: counter/views/home.py:167
550
+msgid "Seum"
551
+msgstr "Seum"
552
+
553
+#: counter/views/home.py:184
554
+msgid "24h ago"
555
+msgstr "il y a 24h"
556
+
557
+#: counter/views/home.py:185
558
+msgid "Now"
559
+msgstr "Présent"
560
+
561
+#: counter/views/home.py:203 counter/views/home.py:208
562
+#: counter/views/home.py:228 counter/views/home.py:233
563
+msgid "Number of seums"
564
+msgstr "Nombre de seums"
565
+
566
+#: counter/views/home.py:228 counter/views/home.py:234
567
+msgid "Month"
568
+msgstr "Mois"
569
+
570
+#: counter/views/home.py:247 counter/views/home.py:252
571
+msgid "Number of given likes"
572
+msgstr "Nombre de likes distribués"
573
+
574
+#: counter/views/home.py:266 counter/views/home.py:272
575
+msgid "Hashtag"
576
+msgstr "Hashtag"
577
+
578
+#: counter/views/home.py:266 counter/views/home.py:271
579
+msgid "Number of seums containing the hashtag"
580
+msgstr "Nombre de seums contenant le hashtag"
581
+
582
+#: counter/views/home.py:285 counter/views/home.py:290
583
+msgid "Number of received likes"
584
+msgstr "Nombre de likes reçus"
585
+
586
+#: counter/views/user.py:22
587
+msgid "Passwords do not match."
588
+msgstr "Les mots de passe sont différents."
589
+
590
+#: counter/views/user.py:27
591
+msgid "A user with this email address already exists."
592
+msgstr "Un utilisateur avec cette adresse email existe déjà."
593
+
594
+#: counter/views/user.py:33
595
+msgid "Use another email address, another user has already this login."
596
+msgstr "Utilise une autre adresse email, un autre utilisateur a déjà ce login."
597
+
598
+#: seum/settings.py:111
599
+msgid "English"
600
+msgstr "Anglais"
601
+
602
+#: seum/settings.py:112
603
+msgid "French"
604
+msgstr "Français"

+ 4 - 1
requirements.txt

@@ -1,4 +1,7 @@
1
-django
1
+arrow==0.10
2 2
 babel
3
+django==1.10
3 4
 django-bootstrap3
5
+django-debug-toolbar==1.6
4 6
 django-graphos-3
7
+pandas==0.19

+ 9 - 1
seum/settings.py.default

@@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/1.9/ref/settings/
12 12
 
13 13
 import os
14 14
 from django.core.urlresolvers import reverse_lazy
15
+from django.utils.translation import ugettext_lazy as _
15 16
 
16 17
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
17 18
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -38,12 +39,14 @@ INSTALLED_APPS = [
38 39
     'django.contrib.sessions',
39 40
     'django.contrib.messages',
40 41
     'django.contrib.staticfiles',
42
+    'django_extensions',
41 43
     'counter'
42 44
 ]
43 45
 
44 46
 MIDDLEWARE_CLASSES = [
45 47
     'django.middleware.security.SecurityMiddleware',
46 48
     'django.contrib.sessions.middleware.SessionMiddleware',
49
+    'django.middleware.locale.LocaleMiddleware',
47 50
     'django.middleware.common.CommonMiddleware',
48 51
     'django.middleware.csrf.CsrfViewMiddleware',
49 52
     'django.contrib.auth.middleware.AuthenticationMiddleware',
@@ -87,7 +90,7 @@ DATABASES = {
87 90
 # Internationalization
88 91
 # https://docs.djangoproject.com/en/1.9/topics/i18n/
89 92
 
90
-LANGUAGE_CODE = 'en-US'
93
+LANGUAGE_CODE = 'en'
91 94
 
92 95
 TIME_ZONE = 'UTC'
93 96
 
@@ -97,6 +100,11 @@ USE_L10N = True
97 100
 
98 101
 USE_TZ = True
99 102
 
103
+LANGUAGES = [
104
+    ('en', _('English')),
105
+    ('fr', _('French')),
106
+]
107
+
100 108
 
101 109
 # Static files (CSS, JavaScript, Images)
102 110
 # https://docs.djangoproject.com/en/1.9/howto/static-files/

+ 15 - 4
seum/urls.py

@@ -13,14 +13,25 @@ Including another URLconf
13 13
     1. Import the include() function: from django.conf.urls import url, include
14 14
     2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
15 15
 """
16
+from django.conf import settings
16 17
 from django.conf.urls import url, include
18
+from django.conf.urls.i18n import i18n_patterns
17 19
 from django.contrib import admin
18 20
 from django.views.generic.base import RedirectView
19 21
 
20
-urlpatterns = [
22
+urlpatterns = [url(r'^i18n/', include('django.conf.urls.i18n'), name='set_language'), ]
23
+
24
+urlpatterns += i18n_patterns(
21 25
     url(r'^admin/', admin.site.urls),
22
-    url(r'^seum/', include('counter.urls')),
26
+
23 27
     url(r'^favicon\.ico$', RedirectView.as_view(url='/static/favicon.ico')),
24 28
     url(r'^robots\.txt$', RedirectView.as_view(url='/static/robots.txt')),
25
-    url(r'^', RedirectView.as_view(url='seum/')),
26
-]
29
+)
30
+
31
+if settings.DEBUG:
32
+    import debug_toolbar
33
+    urlpatterns += i18n_patterns(
34
+        url(r'^__debug__/', include(debug_toolbar.urls)),
35
+    )
36
+
37
+urlpatterns += i18n_patterns(url(r'^', include('counter.urls')), )