diff options
Diffstat (limited to 'hyperkitty')
-rw-r--r-- | hyperkitty/migrations/0007_auto__add_field_tag_user.py | 99 | ||||
-rw-r--r-- | hyperkitty/models.py | 3 | ||||
-rw-r--r-- | hyperkitty/static/css/hyperkitty-message.css | 19 | ||||
-rw-r--r-- | hyperkitty/static/js/hyperkitty.js | 19 | ||||
-rw-r--r-- | hyperkitty/templates/threads/right_col.html | 2 | ||||
-rw-r--r-- | hyperkitty/templates/threads/tags.html | 11 | ||||
-rw-r--r-- | hyperkitty/urls.py | 4 | ||||
-rw-r--r-- | hyperkitty/views/forms.py | 2 | ||||
-rw-r--r-- | hyperkitty/views/thread.py | 44 |
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">×</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), |