BryanDavis has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/358505 )

Change subject: Add support for "tagging" toolinfo records
......................................................................

Add support for "tagging" toolinfo records

Add the ability to create and associate keyword tags with toolinfo
records.

Bug: T149458
Change-Id: Ic2947a1c2e8b44e19804c72d89f95bcd1bf37c0e
---
A contrib/collectstatic.sh
M requirements.txt
M static/css/site.css
M static/js/site.js
M striker/settings.py
M striker/templates/tools/info/create.html
M striker/templates/tools/info/revision/body.html
M striker/templates/tools/info/update.html
M striker/tools/forms.py
A striker/tools/migrations/0007_toolinfo_tags.py
M striker/tools/models.py
M striker/tools/urls.py
M striker/tools/views.py
13 files changed, 115 insertions(+), 2 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/labs/striker 
refs/changes/05/358505/1

diff --git a/contrib/collectstatic.sh b/contrib/collectstatic.sh
new file mode 100755
index 0000000..b79ebaa
--- /dev/null
+++ b/contrib/collectstatic.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+# Ugly collectstatic command to keep django-autocomplete-light from making
+# a mess of the staticfiles directory.
+#
+# Django's collect static maintenance script has an --ignore option, but
+# strangely the way that it is implemented inside the script you can only
+# exclude based on the name of a single directory or file. Ideally you would be
+# able to exclude a more descriptive path such as
+# django-autocomplete-light/vendor/vendor
+#
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+$DIR/manage.sh collectstatic -c --noinput \
+    --ignore i18n \
+    --ignore src \
+    --ignore tests \
+    --ignore *.json \
+    --ignore Gruntfile.js
diff --git a/requirements.txt b/requirements.txt
index b9812b8..717f196 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,6 +3,7 @@
 diff-match-patch>=20121119  # Apache 2.0
 Django<1.9,>=1.8.14  # BSD
 django-auth-ldap>=1.2.8  # BSD
+django-autocomplete-light>=3.2.8  # MIT
 django-bootstrap3<8.0.0,>=7.0.0  # Apache 2.0
 django-csp>=2.0.3  # BSD
 django-formtools>=1.0  # BSD
diff --git a/static/css/site.css b/static/css/site.css
index 2876a38..f665940 100644
--- a/static/css/site.css
+++ b/static/css/site.css
@@ -151,6 +151,12 @@
 .toolinfo-details {
   margin-top:.5em;
 }
+.toolinfo-tags .label-default {
+  background-color:#fdf3da;
+  border-radius:3px;
+  border:1px solid #e9dbcd;
+  color:#726f56;
+}
 .diff del,
 .diff ins {
   color:#333;
diff --git a/static/js/site.js b/static/js/site.js
index d99edcf..1e172a6 100644
--- a/static/js/site.js
+++ b/static/js/site.js
@@ -10,4 +10,11 @@
     notify_refresh_period=60000;
     notify_api_url='/alerts/api/unread_count/';
     register_notifier(fill_notification_badge);
+
+    // Work around for
+    // https://github.com/yourlabs/django-autocomplete-light/issues/772
+    var $csrf = $('form :input[name="csrfmiddlewaretoken"]');
+    if ($csrf.length > 0) {
+        document.csrftoken = $csrf[0].value;
+    }
 })
diff --git a/striker/settings.py b/striker/settings.py
index 63c0abf..1b5c312 100644
--- a/striker/settings.py
+++ b/striker/settings.py
@@ -130,6 +130,8 @@
 
 INSTALLED_APPS = (
     'bootstrap3',
+    'dal',
+    'dal_select2',
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
diff --git a/striker/templates/tools/info/create.html 
b/striker/templates/tools/info/create.html
index d401569..28c8df6 100644
--- a/striker/templates/tools/info/create.html
+++ b/striker/templates/tools/info/create.html
@@ -25,7 +25,7 @@
         </h3>
       </div>
       <div class="panel-body">
-        <form method="post" action="{% url 'tools:info_create' tool=tool.name 
%}" class="form">
+        <form method="post" action="{% url 'tools:info_create' tool=tool.name 
%}" class="form parsley">
           {% csrf_token %}
           {% bootstrap_form form %}
           {% buttons %}
@@ -42,4 +42,5 @@
 {{ block.super }}
 <script lang="javascript" src="{% static 'js/parsley.min.js' %}"></script>
 <script lang="javascript" src="{% static 'js/parsley-bootstrap.js' 
%}"></script>
+{{ form.media }}
 {% endblock %}
diff --git a/striker/templates/tools/info/revision/body.html 
b/striker/templates/tools/info/revision/body.html
index 5f9a3e8..cc40ff4 100644
--- a/striker/templates/tools/info/revision/body.html
+++ b/striker/templates/tools/info/revision/body.html
@@ -1,3 +1,4 @@
+{% load bootstrap3 %}
 {% load i18n %}
 <div class="panel-body toolinfo-body">
   <div class="toolinfo-desc">{{ toolinfo.description|striptags }}</div>
@@ -19,4 +20,9 @@
       {% endif %}
     </dl>
   </div>
+  <p class="toolinfo-tags">
+    {% for tag in toolinfo.tags.all %}
+    <span class="label label-default">{% bootstrap_icon "tag" %} {{ tag.name 
}}</span>
+    {% endfor %}
+  </p>
 </div>
diff --git a/striker/templates/tools/info/update.html 
b/striker/templates/tools/info/update.html
index 7cf2b28..6a32fe3 100644
--- a/striker/templates/tools/info/update.html
+++ b/striker/templates/tools/info/update.html
@@ -38,7 +38,7 @@
         </div>
       </div>
       <div class="panel-body">
-        <form method="post" action="{% url 'tools:info_edit' tool=tool.name 
info_id=toolinfo.pk %}" class="form">
+        <form method="post" action="{% url 'tools:info_edit' tool=tool.name 
info_id=toolinfo.pk %}" class="form parsley">
           {% csrf_token %}
           {% bootstrap_form form %}
           {% buttons %}
@@ -55,4 +55,5 @@
 {{ block.super }}
 <script lang="javascript" src="{% static 'js/parsley.min.js' %}"></script>
 <script lang="javascript" src="{% static 'js/parsley-bootstrap.js' 
%}"></script>
+{{ form.media }}
 {% endblock %}
diff --git a/striker/tools/forms.py b/striker/tools/forms.py
index edc4612..47a821f 100644
--- a/striker/tools/forms.py
+++ b/striker/tools/forms.py
@@ -23,6 +23,7 @@
 from django import forms
 from django.utils.translation import ugettext_lazy as _
 
+from dal import autocomplete
 from parsley.decorators import parsleyfy
 
 from striker import phabricator
@@ -134,6 +135,7 @@
             'name': _('Unique tool name'),
             'title': _('Title'),
             'description': _('Description of tool'),
+            'tags': _('Tags'),
             'license': _('Default software license'),
             'authors': _('Authors'),
             'repository': _('Source code repository'),
@@ -161,6 +163,9 @@
                     'rows': 5,
                 },
             ),
+            'tags': autocomplete.ModelSelect2Multiple(
+                url='tools:tags_autocomplete',
+            ),
             'repository': forms.TextInput(
                 attrs={
                     'placeholder': _("URL to your tool's source code"),
diff --git a/striker/tools/migrations/0007_toolinfo_tags.py 
b/striker/tools/migrations/0007_toolinfo_tags.py
new file mode 100644
index 0000000..006070e
--- /dev/null
+++ b/striker/tools/migrations/0007_toolinfo_tags.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('tools', '0006_monkey_patch_reversion'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ToolInfoTag',
+            options={'ordering': ('name',)},
+            fields=[
+                ('id', models.AutoField(serialize=False, auto_created=True, 
verbose_name='ID', primary_key=True)),
+                ('name', models.CharField(unique=True, max_length=100)),
+                ('slug', models.CharField(unique=True, max_length=100)),
+            ],
+        ),
+        migrations.AddField(
+            model_name='toolinfo',
+            name='tags',
+            field=models.ManyToManyField(blank=True, to='tools.ToolInfoTag'),
+        ),
+    ]
diff --git a/striker/tools/models.py b/striker/tools/models.py
index b0922cc..b1e67da 100644
--- a/striker/tools/models.py
+++ b/striker/tools/models.py
@@ -149,6 +149,17 @@
         return "{} - {}".format(self.slug, self.name)
 
 
+class ToolInfoTag(models.Model):
+    name = models.CharField(max_length=100, unique=True)
+    slug = models.CharField(max_length=100, unique=True)
+
+    def __str__(self):
+        return self.name
+
+    class Meta:
+        ordering = ('name',)
+
+
 @reversion.register()
 class ToolInfo(models.Model):
     """Metadata about a Tool hosted on Tool Labs.
@@ -162,6 +173,7 @@
     license = models.ForeignKey(SoftwareLicense)
     authors = models.ManyToManyField(
         settings.AUTH_USER_MODEL, related_name='+')
+    tags = models.ManyToManyField(ToolInfoTag, blank=True)
     repository = models.CharField(max_length=2047, blank=True, null=True)
     issues = models.CharField(max_length=2047, blank=True, null=True)
     docs = models.CharField(max_length=2047, blank=True, null=True)
diff --git a/striker/tools/urls.py b/striker/tools/urls.py
index d166452..72bed6b 100644
--- a/striker/tools/urls.py
+++ b/striker/tools/urls.py
@@ -107,4 +107,9 @@
         'striker.tools.views.membership_status',
         name='membership_status'
     ),
+    urls.url(
+        r'tags/autocomplete/$',
+        striker.tools.views.ToolInfoTagAutocomplete.as_view(),
+        name='tags_autocomplete'
+    ),
 ]
diff --git a/striker/tools/views.py b/striker/tools/views.py
index 99a868a..2f8b853 100644
--- a/striker/tools/views.py
+++ b/striker/tools/views.py
@@ -33,8 +33,10 @@
 from django.db.utils import DatabaseError
 from django.http import HttpResponseRedirect
 from django.utils import timezone
+from django.utils.text import slugify
 from django.utils.translation import ugettext_lazy as _
 
+from dal import autocomplete
 from notifications.signals import notify
 import reversion
 import reversion.models
@@ -52,6 +54,7 @@
 from striker.tools.models import DiffusionRepo
 from striker.tools.models import Tool
 from striker.tools.models import ToolInfo
+from striker.tools.models import ToolInfoTag
 
 
 WELCOME_MSG = "== Welcome to Tool Labs! ==\n{{subst:ToolsGranted}}"
@@ -665,3 +668,22 @@
         'meta': settings.OAUTH_MWURL,
     }
     return shortcuts.render(req, 'tools/membership/status.html', ctx)
+
+
+class ToolInfoTagAutocomplete(autocomplete.Select2QuerySetView):
+    create_field = 'name'
+
+    def get_queryset(self):
+        if not self.request.user.is_authenticated():
+            return ToolInfoTag.objects.none()
+        qs = ToolInfoTag.objects.all()
+        if self.q:
+            qs = qs.filter(name__icontains=self.q)
+        qs.order_by('name')
+        return qs
+
+    def has_add_permission(self, request):
+        return request.user.is_authenticated()
+
+    def create_object(self, text):
+        return ToolInfoTag.objects.create(name=text, slug=slugify(text))

-- 
To view, visit https://gerrit.wikimedia.org/r/358505
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ic2947a1c2e8b44e19804c72d89f95bcd1bf37c0e
Gerrit-PatchSet: 1
Gerrit-Project: labs/striker
Gerrit-Branch: master
Gerrit-Owner: BryanDavis <bda...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to