Bug 606248 - [editaddon] Add-on Tags

This commit is contained in:
Gregory Koberger 2010-11-11 11:33:39 -08:00
Родитель eccefe9e91
Коммит 564c0df120
9 изменённых файлов: 193 добавлений и 19 удалений

Просмотреть файл

@ -9,14 +9,61 @@ import amo
import captcha.fields
from addons.models import Addon
from amo.utils import slug_validator
from tower import ugettext as _
from translations.widgets import TranslationTextInput, TranslationTextarea
from tags.models import Tag
from tower import ugettext as _, ungettext as ngettext
from translations.widgets import (TranslationTextInput, TranslationTextarea,
TransTextarea)
from translations.fields import TranslatedField, PurifiedField, LinkifiedField
class AddonFormBasic(happyforms.ModelForm):
name = forms.CharField(widget=TranslationTextInput, max_length=50)
slug = forms.CharField(max_length=30)
summary = forms.CharField(widget=TranslationTextarea, max_length=250)
tags = forms.CharField()
def __init__(self, *args, **kw):
self.request = kw.pop('request')
super(AddonFormBasic, self).__init__(*args, **kw)
self.fields['tags'].initial = ', '.join(tag.tag_text for tag in
self.instance.tags.all())
def save(self, addon, commit=False):
tags_new = self.cleaned_data['tags']
tags_old = [t.tag_text for t in addon.tags.all()]
# Add new tags.
for t in set(tags_new) - set(tags_old):
Tag(tag_text=t).save_tag(addon, self.request.amo_user)
# Remove old tags.
for t in set(tags_old) - set(tags_new):
Tag(tag_text=t).remove_tag(addon, self.request.amo_user)
return super(AddonFormBasic, self).save()
def clean_tags(self):
target = [t.strip() for t in self.cleaned_data['tags'].split(',')]
max_tags = amo.MAX_TAGS
min_len = amo.MIN_TAG_LENGTH
total = len(target)
tags_short = [t for t in target if len(t.strip()) < min_len]
if total > max_tags:
raise forms.ValidationError(ngettext(
'You have {0} too many tags.',
'You have {0} too many tags.',
total - max_tags)
.format(total - max_tags))
if tags_short:
raise forms.ValidationError(ngettext(
'All tags must be at least {0} character.',
'All tags must be at least {0} characters.',
min_len).format(min_len))
return target
def clean_slug(self):
target = self.cleaned_data['slug']
@ -32,6 +79,10 @@ class AddonFormDetails(happyforms.ModelForm):
default_locale = forms.TypedChoiceField(choices=Addon.LOCALES)
homepage = forms.URLField(widget=TranslationTextInput)
def __init__(self, *args, **kw):
self.request = kw.pop('request')
super(AddonFormDetails, self).__init__(*args, **kw)
class Meta:
model = Addon
fields = ('description', 'default_locale', 'homepage')
@ -41,7 +92,11 @@ class AddonFormSupport(happyforms.ModelForm):
support_url = forms.URLField(widget=TranslationTextInput)
support_email = forms.EmailField(widget=TranslationTextInput)
def save(self, addon, commit=False):
def __init__(self, *args, **kw):
self.request = kw.pop('request')
super(AddonFormSupport, self).__init__(*args, **kw)
def save(self, addon, commit=True):
instance = self.instance
# If there's a GetSatisfaction URL entered, we'll extract the product
@ -57,7 +112,7 @@ class AddonFormSupport(happyforms.ModelForm):
instance.get_satisfaction_company = company
instance.get_satisfaction_product = product
return super(AddonFormSupport, self).save()
return super(AddonFormSupport, self).save(commit)
class Meta:
model = Addon
@ -68,6 +123,10 @@ class AddonFormTechnical(forms.ModelForm):
developer_comments = forms.CharField(widget=TranslationTextarea,
required=False)
def __init__(self, *args, **kw):
self.request = kw.pop('request')
super(AddonFormTechnical, self).__init__(*args, **kw)
class Meta:
model = Addon
fields = ('developer_comments', 'view_source', 'site_specific',

Просмотреть файл

@ -169,6 +169,10 @@ ADDON_SLUGS = {
ADDON_SEARCH: 'search-tools',
}
# Edit addon information
MAX_TAGS = 20
MIN_TAG_LENGTH = 2
# These types don't maintain app compatibility in the db. Instead, we look at
# APP.types and APP_TYPE_SUPPORT to figure out where they are compatible.
NO_COMPAT = (ADDON_SEARCH, ADDON_PERSONA)

Просмотреть файл

@ -168,18 +168,14 @@ class REQUEST_VERSION:
keep = True
# TODO(gkoberger): When he does 606248
class ADD_TAG:
id = 25
# L10n: {0} is the tag name.
format = _(u'{user.name} added tag {0} to {addon}')
format = _(u'{tag} added to {addon}')
# TODO(gkoberger): When he does 606248
class REMOVE_TAG:
id = 26
# L10n: {0} is the tag name.
format = _(u'{user.name} removed tag {0} from {addon}')
format = _(u'{tag} removed from {addon}')
class ADD_TO_COLLECTION:

Просмотреть файл

@ -13,6 +13,7 @@ import amo.models
from addons.models import Addon
from bandwagon.models import Collection
from reviews.models import Review
from tags.models import Tag
from translations.fields import TranslatedField
from users.models import UserProfile
from versions.models import Version
@ -180,6 +181,8 @@ class ActivityLog(amo.models.ModelBase):
review = None
version = None
collection = None
tag = None
for arg in self.arguments:
if isinstance(arg, Addon) and not addon:
addon = u'<a href="%s">%s</a>' % (arg.get_url_path(), arg.name)
@ -196,7 +199,9 @@ class ActivityLog(amo.models.ModelBase):
collection = u'<a href="%s">%s</a>' % (arg.get_url_path(),
arg.name)
arguments.remove(arg)
if isinstance(arg, Tag) and not tag:
tag = u'<a href="%s">%s</a>' % (arg.get_url_path(),
arg.tag_text)
try:
data = dict(user=self.user, addon=addon, review=review,
version=version, collection=collection)

Просмотреть файл

@ -81,10 +81,16 @@
descriptors such as tabs, toolbar, or twitter. You
may have a maximum of {0} tags.").format(amo.MAX_TAGS)) }}
</th>
<td>
<td id="addon_tags_edit">
{% if editable %}
{# TODO(gkoberger): Add tags #}
<strong>Coming Soon</strong>
{{ form.tags|safe }}
{{ form.tags.errors|safe }}
<div class="edit-addon-details">
{{ ngettext('Comma-separated, minimum of {0} character.',
'Comma-separated, minimum of {0} characters.',
amo.MIN_TAG_LENGTH)|f(amo.MIN_TAG_LENGTH) }}
{{ _('Example: ocean, sail boat, water.') }}
</div>
{% else %}
{% call empty_unless(tags) %}
{{ tags|join(', ') }}

Просмотреть файл

@ -22,6 +22,7 @@ from devhub.forms import ContribForm
from devhub.models import ActivityLog, RssKey
from files.models import File, Platform
from reviews.models import Review
from tags.models import Tag
from users.models import UserProfile
from versions.models import ApplicationsVersions, License, Version
@ -910,6 +911,11 @@ class TestEdit(test_utils.TestCase):
self.addon = self.get_addon()
assert self.client.login(username='del@icio.us', password='password')
self.url = reverse('devhub.addons.edit', args=[self.addon.id])
self.user = UserProfile.objects.get(pk=55021)
self.tags = ['tag3', 'tag2', 'tag1']
for t in self.tags:
Tag(tag_text=t).save_tag(self.addon, self.user)
def get_addon(self):
return Addon.objects.no_cache().get(id=3615)
@ -932,7 +938,8 @@ class TestEdit(test_utils.TestCase):
data = dict(name='new name',
slug='test_addon',
summary='new summary')
summary='new summary',
tags=', '.join(self.tags))
r = self.client.post(self.get_url('basic', True), data)
eq_(r.status_code, 200)
@ -944,20 +951,100 @@ class TestEdit(test_utils.TestCase):
eq_(unicode(addon.slug), data['slug'])
eq_(unicode(addon.summary), data['summary'])
self.tags.sort()
eq_([unicode(t) for t in addon.tags.all()], self.tags)
def test_edit_basic_slugs_unique(self):
Addon.objects.get(id=5579).update(slug='test_slug')
data = dict(name='new name',
slug='test_slug',
summary='new summary')
summary='new summary',
tags=','.join(self.tags))
r = self.client.post(self.get_url('basic', True), data)
eq_(r.status_code, 200)
self.assertFormError(r, 'form', 'slug', 'This slug is already in use.')
def test_edit_basic_name_not_empty(self):
def test_edit_basic_add_tag(self):
count = ActivityLog.objects.all().count()
self.tags.insert(0, 'tag4')
data = dict(name='new name',
slug='test_slug',
summary='new summary',
tags=', '.join(self.tags))
r = self.client.post(self.get_url('basic', True), data)
eq_(r.status_code, 200)
doc = pq(r.content)
result = doc('#addon_tags_edit').eq(0).text()
self.tags.sort()
eq_(result, ', '.join(self.tags))
eq_(ActivityLog.objects.filter(action=amo.LOG.ADD_TAG.id).count(),
count + 1)
def test_edit_basic_remove_tag(self):
self.tags.remove('tag2')
count = ActivityLog.objects.all().count()
data = dict(name='new name',
slug='test_slug',
summary='new summary',
tags=', '.join(self.tags))
r = self.client.post(self.get_url('basic', True), data)
eq_(r.status_code, 200)
doc = pq(r.content)
result = doc('#addon_tags_edit').eq(0).text()
self.tags.sort()
eq_(result, ', '.join(self.tags))
eq_(ActivityLog.objects.filter(action=amo.LOG.REMOVE_TAG.id).count(),
count + 1)
def test_edit_basic_minlength_tags(self):
tags = self.tags
tags.append('a' * (amo.MIN_TAG_LENGTH - 1))
data = dict(name='new name',
slug='test_slug',
summary='new summary',
tags=', '.join(tags))
r = self.client.post(self.get_url('basic', True), data)
eq_(r.status_code, 200)
self.assertFormError(r, 'form', 'tags',
'All tags must be at least %d characters.' %
amo.MIN_TAG_LENGTH)
def test_edit_basic_max_tags(self):
tags = self.tags
for i in range(amo.MAX_TAGS + 1):
tags.append('test%d' % i)
data = dict(name='new name',
slug='test_slug',
summary='new summary',
tags=', '.join(tags))
r = self.client.post(self.get_url('basic', True), data)
eq_(r.status_code, 200)
self.assertFormError(r, 'form', 'tags', 'You have %d too many tags.' %
(len(tags) - amo.MAX_TAGS))
def test_edit_basic_name_not_empty(self):
data = dict(name='',
slug=self.addon.slug,
summary=self.addon.summary)

Просмотреть файл

@ -414,13 +414,13 @@ def addons_section(request, addon_id, addon, section, editable=False):
if editable:
if request.method == 'POST':
form = models[section](request.POST, request.FILES,
instance=addon)
instance=addon, request=request)
if form.is_valid():
addon = form.save(addon)
editable = False
amo.log(amo.LOG.EDIT_PROPERTIES, addon)
else:
form = models[section](instance=addon)
form = models[section](instance=addon, request=request)
else:
form = False

Просмотреть файл

@ -38,6 +38,17 @@ class Tag(amo.models.ModelBase):
return urls
def save_tag(self, addon, user):
tag, _ = Tag.objects.get_or_create(tag_text=self.tag_text)
AddonTag.objects.get_or_create(addon=addon, tag=tag, user=user)
amo.log(amo.LOG.ADD_TAG, tag, addon)
return tag
def remove_tag(self, addon, user):
tag, created = Tag.objects.get_or_create(tag_text=self.tag_text)
AddonTag.objects.filter(addon=addon, tag=tag).delete()
amo.log(amo.LOG.REMOVE_TAG, tag, addon)
class TagStat(amo.models.ModelBase):
tag = models.OneToOneField(Tag, primary_key=True)

Просмотреть файл

@ -177,6 +177,12 @@ a.remove:hover,
overflow: hidden;
}
.edit-addon-details {
padding-top: 3px;
font-size: 0.8em;
color: #555;
}
/* @end */
/* @group Version Compatibility */