summaryrefslogtreecommitdiffstats
path: root/hyperkitty
diff options
context:
space:
mode:
Diffstat (limited to 'hyperkitty')
-rw-r--r--hyperkitty/migrations/0007_auto__add_field_tag_user.py99
-rw-r--r--hyperkitty/models.py3
-rw-r--r--hyperkitty/static/css/hyperkitty-message.css19
-rw-r--r--hyperkitty/static/js/hyperkitty.js19
-rw-r--r--hyperkitty/templates/threads/right_col.html2
-rw-r--r--hyperkitty/templates/threads/tags.html11
-rw-r--r--hyperkitty/urls.py4
-rw-r--r--hyperkitty/views/forms.py2
-rw-r--r--hyperkitty/views/thread.py44
9 files changed, 176 insertions, 27 deletions
diff --git a/hyperkitty/migrations/0007_auto__add_field_tag_user.py b/hyperkitty/migrations/0007_auto__add_field_tag_user.py
new file mode 100644
index 0000000..75bcd82
--- /dev/null
+++ b/hyperkitty/migrations/0007_auto__add_field_tag_user.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding field 'Tag.user'
+ db.add_column(u'hyperkitty_tag', 'user',
+ self.gf('django.db.models.fields.related.ForeignKey')(default=1, to=orm['auth.User']),
+ keep_default=False)
+
+
+ def backwards(self, orm):
+ # Deleting field 'Tag.user'
+ db.delete_column(u'hyperkitty_tag', 'user_id')
+
+
+ models = {
+ u'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ u'auth.permission': {
+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ u'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ u'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ u'hyperkitty.favorite': {
+ 'Meta': {'object_name': 'Favorite'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'list_address': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'threadid': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
+ },
+ u'hyperkitty.lastview': {
+ 'Meta': {'object_name': 'LastView'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'list_address': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'threadid': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
+ 'view_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
+ },
+ u'hyperkitty.rating': {
+ 'Meta': {'object_name': 'Rating'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'list_address': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'messageid': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
+ 'vote': ('django.db.models.fields.SmallIntegerField', [], {})
+ },
+ u'hyperkitty.tag': {
+ 'Meta': {'object_name': 'Tag'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'list_address': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tag': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'threadid': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
+ },
+ u'hyperkitty.userprofile': {
+ 'Meta': {'object_name': 'UserProfile'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'karma': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+ 'timezone': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '100'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
+ }
+ }
+
+ complete_apps = ['hyperkitty']
diff --git a/hyperkitty/models.py b/hyperkitty/models.py
index 8b2e514..4c18d55 100644
--- a/hyperkitty/models.py
+++ b/hyperkitty/models.py
@@ -66,10 +66,9 @@ class UserProfile(models.Model):
class Tag(models.Model):
# @TODO: instead of list_address, user list model from kittystore?
list_address = models.CharField(max_length=255, db_index=True)
-
# @TODO: instead of threadid, use thread model from kittystore?
threadid = models.CharField(max_length=100, db_index=True)
-
+ user = models.ForeignKey(User)
tag = models.CharField(max_length=255)
def __unicode__(self):
diff --git a/hyperkitty/static/css/hyperkitty-message.css b/hyperkitty/static/css/hyperkitty-message.css
index a960853..2dfb9df 100644
--- a/hyperkitty/static/css/hyperkitty-message.css
+++ b/hyperkitty/static/css/hyperkitty-message.css
@@ -104,7 +104,9 @@
margin: 0 2px 0 2px;
}
+
/* Tags */
+
#tags {
color: rgb(167, 169, 172);
margin-top: 2em;
@@ -126,11 +128,28 @@
margin-left: 5px;
}
+#tags .rmtag {
+ margin: 0;
+ display: inline;
+}
+#tags .rmtag a {
+ visibility: hidden;
+ font-size: 125%;
+}
+#tags:hover .rmtag a {
+ visibility: visible;
+}
+#tags:hover .rmtag a:hover {
+ text-decoration: none;
+}
+
#add-tag {
margin-top: 0.3em;
}
+
/* Participants */
+
#participants {
margin-top: 2em;
color: rgb(167, 169, 172);
diff --git a/hyperkitty/static/js/hyperkitty.js b/hyperkitty/static/js/hyperkitty.js
index ffdf5e1..49bee91 100644
--- a/hyperkitty/static/js/hyperkitty.js
+++ b/hyperkitty/static/js/hyperkitty.js
@@ -77,8 +77,9 @@ function setup_vote(baseElem) {
* Tagging
*/
-function setup_add_tag() {
- $("#add-tag-form").submit( function () {
+function setup_tags() {
+ function post_tags(e) {
+ e.preventDefault();
$.ajax({
type: "POST",
dataType: "json",
@@ -86,13 +87,23 @@ function setup_add_tag() {
url: $(this).attr("action"),
success: function(data) {
$("#tags").html(data.html);
+ $("#tags form").submit(post_tags);
+ $("#tags form a").click(function(e) {
+ e.preventDefault();
+ $(this).parents("form").first().submit();
+ });
},
error: function(jqXHR, textStatus, errorThrown) {
// authentication and invalid data
alert(jqXHR.responseText);
}
});
- return false;
+ }
+ $("#add-tag-form").submit(post_tags);
+ $("#tags form").submit(post_tags);
+ $("#tags form a").click(function(e) {
+ e.preventDefault();
+ $(this).parents("form").first().submit();
});
}
@@ -531,7 +542,7 @@ function setup_flash_messages() {
$(document).ready(function() {
setup_vote();
- setup_add_tag();
+ setup_tags();
setup_months_list();
setup_favorites();
setup_emails_list();
diff --git a/hyperkitty/templates/threads/right_col.html b/hyperkitty/templates/threads/right_col.html
index afbef7f..c4fe161 100644
--- a/hyperkitty/templates/threads/right_col.html
+++ b/hyperkitty/templates/threads/right_col.html
@@ -43,7 +43,7 @@
</div>
<div id="add-tag">
<form id="add-tag-form" name="addtag" method="post"
- action="{% url 'add_tag' mlist_fqdn=mlist.name threadid=threadid %}">
+ action="{% url 'tags' mlist_fqdn=mlist.name threadid=threadid %}">
{% csrf_token %}
{{ addtag_form.as_p }}
</form>
diff --git a/hyperkitty/templates/threads/tags.html b/hyperkitty/templates/threads/tags.html
index 236e4c4..1ec4989 100644
--- a/hyperkitty/templates/threads/tags.html
+++ b/hyperkitty/templates/threads/tags.html
@@ -5,7 +5,16 @@
<ul class="inline">
{% for tag in tags %}
<li>
- <a href="{% url 'search_tag' mlist_fqdn=mlist.name tag=tag.tag %}" >{{ tag.tag }}</a>
+ <a href="{% url 'search_tag' mlist_fqdn=mlist.name tag=tag.tag %}"
+ title="Search for tag {{ tag.tag|escape }}">{{ tag.tag }}</a>
+ {% if user == tag.user %}
+ <form method="post" class="rmtag" action="{% url 'tags' mlist_fqdn=mlist.name threadid=threadid %}">
+ {% csrf_token %}
+ <input type="hidden" name="action" value="rm" />
+ <input type="hidden" name="tag" value="{{ tag.tag|escape }}" />
+ <a href="#rmtag" title="Remove">&times;</a>
+ </form>
+ {% endif %}
{% if not forloop.last %} <span>|</span> {% endif %}
</li>
{% endfor %}
diff --git a/hyperkitty/urls.py b/hyperkitty/urls.py
index 10143b1..2689413 100644
--- a/hyperkitty/urls.py
+++ b/hyperkitty/urls.py
@@ -77,8 +77,8 @@ urlpatterns = patterns('hyperkitty.views',
'thread.thread_index', name='thread'),
url(r'^list/(?P<mlist_fqdn>[^/@]+@[^/@]+)/thread/(?P<threadid>\w+)/replies$',
'thread.replies', name='thread_replies'),
- url(r'^list/(?P<mlist_fqdn>[^/@]+@[^/@]+)/thread/(?P<threadid>\w+)/addtag$',
- 'thread.add_tag', name='add_tag'),
+ url(r'^list/(?P<mlist_fqdn>[^/@]+@[^/@]+)/thread/(?P<threadid>\w+)/tags$',
+ 'thread.tags', name='tags'),
url(r'^list/(?P<mlist_fqdn>[^/@]+@[^/@]+)/thread/(?P<threadid>\w+)/favorite$',
'thread.favorite', name='favorite'),
diff --git a/hyperkitty/views/forms.py b/hyperkitty/views/forms.py
index 938deba..62175e7 100644
--- a/hyperkitty/views/forms.py
+++ b/hyperkitty/views/forms.py
@@ -94,7 +94,7 @@ class AddTagForm(forms.Form):
'button_text': 'Add'}
)
)
- from_url = forms.CharField(widget=forms.HiddenInput, required=False)
+ action = forms.CharField(widget=forms.HiddenInput, initial="add")
diff --git a/hyperkitty/views/thread.py b/hyperkitty/views/thread.py
index fd52831..fa1b860 100644
--- a/hyperkitty/views/thread.py
+++ b/hyperkitty/views/thread.py
@@ -81,10 +81,8 @@ def thread_index(request, mlist_fqdn, threadid, month=None, year=None):
sort_mode = request.GET.get("sort", "thread")
set_message_votes(thread.starting_email, request.user)
- from_url = reverse("thread", kwargs={"mlist_fqdn":mlist_fqdn,
- "threadid":threadid})
# Tags
- tag_form = AddTagForm(initial={'from_url' : from_url})
+ tag_form = AddTagForm()
try:
tags = Tag.objects.filter(threadid=threadid, list_address=mlist_fqdn)
except Tag.DoesNotExist:
@@ -200,26 +198,38 @@ def replies(request, mlist_fqdn, threadid):
mimetype='application/javascript')
-def add_tag(request, mlist_fqdn, threadid):
- """ Add a tag to a given thread. """
+def tags(request, mlist_fqdn, threadid):
+ """ Add or remove a tag on a given thread. """
if not request.user.is_authenticated():
return HttpResponse('You must be logged in to add a tag',
content_type="text/plain", status=403)
if request.method != 'POST':
raise SuspiciousOperation
-
- form = AddTagForm(request.POST)
- if not form.is_valid():
- return HttpResponse("Error adding tag: invalid data",
- content_type="text/plain", status=500)
- tag = form.data['tag']
+ action = request.POST.get("action")
+
+ if action == "add":
+ form = AddTagForm(request.POST)
+ if not form.is_valid():
+ return HttpResponse("Error adding tag: invalid data",
+ content_type="text/plain", status=500)
+ tagname = form.data['tag']
+ elif action == "rm":
+ tagname = request.POST.get('tag')
+ else:
+ raise SuspiciousOperation
try:
- tag_obj = Tag.objects.get(threadid=threadid,
- list_address=mlist_fqdn, tag=tag)
+ tag = Tag.objects.get(threadid=threadid, list_address=mlist_fqdn,
+ tag=tagname)
+ if action == "rm":
+ tag.delete()
except Tag.DoesNotExist:
- tag_obj = Tag(list_address=mlist_fqdn, threadid=threadid, tag=tag)
- tag_obj.save()
+ if action == "add":
+ tag = Tag(list_address=mlist_fqdn, threadid=threadid,
+ tag=tagname, user=request.user)
+ tag.save()
+ elif action == "rm":
+ raise Http404("No such tag: %s" % tagname)
# Now refresh the tag list
tags = Tag.objects.filter(threadid=threadid, list_address=mlist_fqdn)
@@ -227,7 +237,9 @@ def add_tag(request, mlist_fqdn, threadid):
tpl = loader.get_template('threads/tags.html')
html = tpl.render(RequestContext(request, {
"tags": tags,
- "mlist": FakeMList(name=mlist_fqdn)}))
+ "mlist": FakeMList(name=mlist_fqdn),
+ "threadid": threadid,
+ }))
response = {"tags": [ t.tag for t in tags ], "html": html}
return HttpResponse(json.dumps(response),