Bug 606248 - [editaddon] Add-on Tags
This commit is contained in:
Родитель
eccefe9e91
Коммит
564c0df120
|
@ -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 */
|
||||
|
|
Загрузка…
Ссылка в новой задаче