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