submission step 3, categories for multiple apps (bug 620422, bug 618238)
This commit is contained in:
Родитель
de6eb48613
Коммит
6113c2c3d9
|
@ -3,15 +3,18 @@ import re
|
|||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.forms.formsets import BaseFormSet, formset_factory
|
||||
|
||||
import happyforms
|
||||
from tower import ugettext as _, ungettext as ngettext
|
||||
|
||||
import amo
|
||||
import captcha.fields
|
||||
from amo.utils import ImageCheck, slug_validator, slugify, remove_icons
|
||||
from amo.utils import (ImageCheck, slug_validator, slugify, sorted_groupby,
|
||||
remove_icons)
|
||||
from addons.models import Addon, ReverseNameLookup, Category, AddonCategory
|
||||
from addons.widgets import IconWidgetRenderer, CategoriesSelectMultiple
|
||||
from applications.models import Application
|
||||
from devhub import tasks
|
||||
from tags.models import Tag
|
||||
from translations.fields import TransField, TransTextarea
|
||||
|
@ -45,8 +48,6 @@ class AddonFormBasic(AddonFormBase):
|
|||
summary = TransField(widget=TransTextarea(attrs={'rows': 4}),
|
||||
max_length=250)
|
||||
tags = forms.CharField(required=False)
|
||||
categories = forms.ModelMultipleChoiceField(queryset=False,
|
||||
widget=CategoriesSelectMultiple)
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(AddonFormBasic, self).__init__(*args, **kw)
|
||||
|
@ -59,12 +60,6 @@ class AddonFormBasic(AddonFormBase):
|
|||
name_validators.append(validate_name)
|
||||
self.fields['name'].validators = name_validators
|
||||
|
||||
# TODO(gkoberger/help from chowse):
|
||||
# Make it so the categories aren't hardcoded as Firefox only
|
||||
self.fields['categories'].queryset = (order_by_translation(
|
||||
Category.objects.filter(application=1, type=self.instance.type),
|
||||
'name'))
|
||||
|
||||
def save(self, addon, commit=False):
|
||||
tags_new = self.cleaned_data['tags']
|
||||
tags_old = [slugify(t.tag_text, spaces=True) for t in addon.tags.all()]
|
||||
|
@ -77,17 +72,6 @@ class AddonFormBasic(AddonFormBase):
|
|||
for t in set(tags_old) - set(tags_new):
|
||||
Tag(tag_text=t).remove_tag(addon, amo.get_user())
|
||||
|
||||
categories_new = self.cleaned_data['categories']
|
||||
categories_old = list(addon.categories.all())
|
||||
|
||||
# Add new categories.
|
||||
for c in set(categories_new) - set(categories_old):
|
||||
AddonCategory(addon=addon, category=c).save()
|
||||
|
||||
# Remove old categories.
|
||||
for c in set(categories_old) - set(categories_new):
|
||||
AddonCategory.objects.filter(addon=addon, category=c).delete()
|
||||
|
||||
# We ignore `commit`, since we need it to be `False` so we can save
|
||||
# the ManyToMany fields on our own.
|
||||
addonform = super(AddonFormBasic, self).save(commit=False)
|
||||
|
@ -140,26 +124,82 @@ class AddonFormBasic(AddonFormBase):
|
|||
raise forms.ValidationError(_('This slug is already in use.'))
|
||||
return target
|
||||
|
||||
|
||||
class ApplicationChoiceField(forms.ModelChoiceField):
|
||||
|
||||
def label_from_instance(self, obj):
|
||||
return obj.id
|
||||
|
||||
|
||||
class CategoryForm(forms.Form):
|
||||
application = ApplicationChoiceField(Application.objects.all(),
|
||||
widget=forms.HiddenInput)
|
||||
categories = forms.ModelMultipleChoiceField(
|
||||
queryset=Category.objects.all(), widget=CategoriesSelectMultiple)
|
||||
|
||||
def save(self, addon):
|
||||
categories_new = self.cleaned_data['categories']
|
||||
categories_old = [cats for app, cats in addon.app_categories
|
||||
if app.id == self.cleaned_data['application'].id]
|
||||
if categories_old:
|
||||
categories_old = categories_old[0]
|
||||
|
||||
# Add new categories.
|
||||
for c in set(categories_new) - set(categories_old):
|
||||
AddonCategory(addon=addon, category=c).save()
|
||||
|
||||
# Remove old categories.
|
||||
for c in set(categories_old) - set(categories_new):
|
||||
AddonCategory.objects.filter(addon=addon, category=c).delete()
|
||||
|
||||
def clean_categories(self):
|
||||
# TODO(gkoberger): When we support multiple categories, this needs
|
||||
# to be changed so they can have 2 categories per application.
|
||||
categories = self.cleaned_data['categories']
|
||||
total = categories.count()
|
||||
max_cat = amo.MAX_CATEGORIES
|
||||
total = len(self.cleaned_data['categories'].all())
|
||||
if total > max_cat:
|
||||
raise forms.ValidationError(ngettext(
|
||||
'You can only have {0} category.',
|
||||
'You can only have {0} categories.',
|
||||
max_cat).format(max_cat))
|
||||
'You can have only {0} category.',
|
||||
'You can have only {0} categories.',
|
||||
max_cat).format(max_cat))
|
||||
|
||||
has_other = len([i.name for i in categories if i.name=="Other"]) > 0
|
||||
has_misc = filter(lambda x: x.misc, categories)
|
||||
if has_misc and total > 1:
|
||||
raise forms.ValidationError(
|
||||
_("The miscellaneous category cannot be combined with "
|
||||
"additional categories."))
|
||||
|
||||
if has_other and total > 1:
|
||||
raise forms.ValidationError(_("The category 'Other' can not be "
|
||||
"combined with additional "
|
||||
"categories."))
|
||||
return categories
|
||||
|
||||
return self.cleaned_data['categories']
|
||||
|
||||
class BaseCategoryFormSet(BaseFormSet):
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
self.addon = kw.pop('addon')
|
||||
super(BaseCategoryFormSet, self).__init__(*args, **kw)
|
||||
self.initial = []
|
||||
apps = sorted(self.addon.compatible_apps.keys(), key=lambda x: x.id)
|
||||
for app in apps:
|
||||
cats = [c for a, c in self.addon.app_categories if a == app]
|
||||
if cats:
|
||||
cats = cats[0]
|
||||
self.initial.append({'categories': [c.id for c in cats]})
|
||||
self._construct_forms()
|
||||
|
||||
for app, form in zip(apps, self.forms):
|
||||
form.initial['application'] = app.id
|
||||
form.app = app
|
||||
|
||||
cats = order_by_translation(Category.objects.filter(
|
||||
type=self.addon.type, application=app.id), 'name')
|
||||
form.fields['categories'].choices = [(c.id, c.name) for c in cats]
|
||||
|
||||
def save(self):
|
||||
for f in self.forms:
|
||||
f.save(self.addon)
|
||||
|
||||
|
||||
CategoryFormSet = formset_factory(form=CategoryForm,
|
||||
formset=BaseCategoryFormSet, extra=0)
|
||||
|
||||
|
||||
def icons():
|
||||
|
|
|
@ -27,6 +27,7 @@ from files.models import File
|
|||
from reviews.models import Review
|
||||
from stats.models import AddonShareCountTotal
|
||||
from translations.fields import TranslatedField, PurifiedField, LinkifiedField
|
||||
from translations.query import order_by_translation
|
||||
from users.models import UserProfile, PersonaAuthor, UserForeignKey
|
||||
from versions.compare import version_int
|
||||
from versions.models import Version
|
||||
|
@ -782,6 +783,16 @@ class Addon(amo.models.ModelBase):
|
|||
def all_categories(self):
|
||||
return list(self.categories.all())
|
||||
|
||||
@property
|
||||
def app_categories(self):
|
||||
categories = sorted_groupby(order_by_translation(self.categories.all(),
|
||||
'name'),
|
||||
key=lambda x: x.application_id)
|
||||
app_cats = []
|
||||
for app, cats in categories:
|
||||
app_cats.append((amo.APP_IDS[app], list(cats)))
|
||||
return app_cats
|
||||
|
||||
|
||||
def update_name_table(sender, **kw):
|
||||
from . import cron
|
||||
|
@ -1031,6 +1042,7 @@ class Category(amo.models.ModelBase):
|
|||
count = models.IntegerField('Addon count')
|
||||
weight = models.IntegerField(
|
||||
help_text='Category weight used in sort ordering')
|
||||
misc = models.BooleanField(default=False)
|
||||
|
||||
addons = models.ManyToManyField(Addon, through='AddonCategory')
|
||||
|
||||
|
|
|
@ -17,9 +17,9 @@ import amo
|
|||
import files.tests
|
||||
from amo import set_user
|
||||
from amo.signals import _connect, _disconnect
|
||||
from addons.models import (Addon, AddonDependency, AddonRecommendation,
|
||||
AddonType, BlacklistedGuid, Category, Charity,
|
||||
Feature, Persona, Preview)
|
||||
from addons.models import (Addon, AddonCategory, AddonDependency,
|
||||
AddonRecommendation, AddonType, BlacklistedGuid,
|
||||
Category, Charity, Feature, Persona, Preview)
|
||||
from applications.models import Application, AppVersion
|
||||
from devhub.models import ActivityLog
|
||||
from files.models import File, Platform
|
||||
|
@ -88,6 +88,7 @@ class TestAddonModels(test_utils.TestCase):
|
|||
'base/addon_6704_grapple.json',
|
||||
'base/addon_4594_a9',
|
||||
'base/addon_4664_twitterbar',
|
||||
'base/thunderbird',
|
||||
'addons/featured',
|
||||
'addons/invalid_latest_version']
|
||||
|
||||
|
@ -283,6 +284,36 @@ class TestAddonModels(test_utils.TestCase):
|
|||
a.save()
|
||||
assert addon().has_eula
|
||||
|
||||
def test_app_categories(self):
|
||||
addon = lambda: Addon.objects.get(pk=3615)
|
||||
|
||||
c22 = Category.objects.get(id=22)
|
||||
c22.name = 'CCC'
|
||||
c22.save()
|
||||
c23 = Category.objects.get(id=23)
|
||||
c23.name = 'BBB'
|
||||
c23.save()
|
||||
c24 = Category.objects.get(id=24)
|
||||
c24.name = 'AAA'
|
||||
c24.save()
|
||||
|
||||
cats = addon().all_categories
|
||||
eq_(cats, [c22, c23, c24])
|
||||
for cat in cats:
|
||||
eq_(cat.application.id, amo.FIREFOX.id)
|
||||
|
||||
cats = [c24, c23, c22]
|
||||
app_cats = [(amo.FIREFOX, cats)]
|
||||
eq_(addon().app_categories, app_cats)
|
||||
|
||||
tb = Application.objects.get(id=amo.THUNDERBIRD.id)
|
||||
c = Category(application=tb, name='XXX', type=addon().type, count=1,
|
||||
weight=1)
|
||||
c.save()
|
||||
ac = AddonCategory(addon=addon(), category=c).save()
|
||||
app_cats += [(amo.THUNDERBIRD, [c])]
|
||||
eq_(addon().app_categories, app_cats)
|
||||
|
||||
def test_review_replies(self):
|
||||
"""
|
||||
Make sure that developer replies are not returned as if they were
|
||||
|
|
|
@ -5,6 +5,8 @@ from django.utils.html import conditional_escape
|
|||
from django.utils.safestring import mark_safe
|
||||
from tower import ugettext as _
|
||||
|
||||
from addons.models import Category
|
||||
|
||||
|
||||
class IconWidgetRenderer(forms.RadioSelect.renderer):
|
||||
""" Return radiobox as a list of images. """
|
||||
|
@ -26,52 +28,42 @@ class IconWidgetRenderer(forms.RadioSelect.renderer):
|
|||
|
||||
|
||||
class CategoriesSelectMultiple(forms.CheckboxSelectMultiple):
|
||||
"""
|
||||
Widget that formats the Categories checkboxes.
|
||||
"""
|
||||
"""Widget that formats the Categories checkboxes."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(self.__class__, self).__init__(**kwargs)
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
if value is None: value = []
|
||||
value = value or []
|
||||
has_id = attrs and 'id' in attrs
|
||||
final_attrs = self.build_attrs(attrs, name=name)
|
||||
|
||||
choices = []
|
||||
other = None
|
||||
|
||||
miscs = Category.objects.filter(misc=True).values_list('id', flat=True)
|
||||
for c in self.choices:
|
||||
if c[1] == 'Other':
|
||||
if c[0] in miscs:
|
||||
other = (c[0],
|
||||
_("My add-on doesn't fit into any of the categories"))
|
||||
else:
|
||||
choices.append(c)
|
||||
|
||||
choices= list(enumerate(choices))
|
||||
choices = list(enumerate(choices))
|
||||
choices_size = len(choices)
|
||||
|
||||
columns = []
|
||||
|
||||
# Left column
|
||||
columns.append(choices[:len(choices) / 2])
|
||||
|
||||
# Right column
|
||||
columns.append(choices[len(choices) / 2:])
|
||||
|
||||
# "Other" column
|
||||
groups = [choices]
|
||||
if other:
|
||||
columns.append([(choices_size,other)])
|
||||
groups.append([(choices_size, other)])
|
||||
|
||||
str_values = set([force_unicode(v) for v in value])
|
||||
|
||||
output = []
|
||||
for (k, column) in enumerate(columns):
|
||||
if k == 2:
|
||||
# We know it's the "other" column
|
||||
output.append(u'<ul class="other">')
|
||||
else:
|
||||
output.append(u'<ul>')
|
||||
for (k, group) in enumerate(groups):
|
||||
cls = 'addon-misc-category' if k == 1 else 'addon-categories'
|
||||
output.append(u'<ul class="%s">' % cls)
|
||||
|
||||
str_values = set([force_unicode(v) for v in value])
|
||||
for i, (option_value, option_label) in column:
|
||||
for i, (option_value, option_label) in group:
|
||||
if has_id:
|
||||
final_attrs = dict(final_attrs, id='%s_%s' % (
|
||||
attrs['id'], i))
|
||||
|
@ -86,7 +78,7 @@ class CategoriesSelectMultiple(forms.CheckboxSelectMultiple):
|
|||
option_label = conditional_escape(force_unicode(option_label))
|
||||
output.append(u'<li><label%s>%s %s</label></li>' % (
|
||||
label_for, rendered_cb, option_label))
|
||||
|
||||
output.append(u'</ul>')
|
||||
|
||||
return mark_safe(u'\n'.join(output))
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% from "devhub/includes/macros.html" import some_html_tip %}
|
||||
{% from "devhub/includes/macros.html" import some_html_tip, select_cats %}
|
||||
{% extends "devhub/addons/submit/base.html" %}
|
||||
|
||||
{% block title %}{{ dev_page_title(_('Step 3'), addon) }}{% endblock %}
|
||||
|
@ -11,56 +11,60 @@
|
|||
<label for="id_name">{{ _("Your add-on's name and version:") }}</label>
|
||||
{{ form.name|safe }}
|
||||
|
||||
{% set version = addon.current_version %}
|
||||
<input type="text" disabled id="current_version"
|
||||
value="{{ version.version }}" size="6" />
|
||||
{% set version = addon.current_version %}
|
||||
<input type="text" disabled id="current_version"
|
||||
value="{{ version.version }}" size="6">
|
||||
|
||||
{{ form.name.errors|safe }}
|
||||
</div>
|
||||
<div class="addon-submission-field">
|
||||
<label>{{ _("Your add-on's detail page will be:") }}</label>
|
||||
<span id="slug_edit" class="edit_with_prefix edit_initially_hidden">
|
||||
<span>{{ settings.SITE_URL }}</span>{{ form.slug|safe }}
|
||||
</span>
|
||||
<span id="slug_readonly">
|
||||
{{ settings.SITE_URL }}/…/<span id="slug_value"></span>
|
||||
<a id="edit_slug" href="#">{{ _('Edit') }}</a>
|
||||
</span>
|
||||
{{ form.slug.errors|safe }}
|
||||
</div>
|
||||
<div class="addon-submission-field">
|
||||
<label>{{ _('Provide a brief summary of your add-on:') }}</label>
|
||||
{{ form.summary|safe }}
|
||||
{{ form.summary.errors|safe }}
|
||||
<div class="edit-addon-details">
|
||||
{% trans %}
|
||||
This summary will be shown with your add-on in listings and searches.
|
||||
{% endtrans %}
|
||||
<div class="char-count"
|
||||
data-for-startswith="{{ form.summary.auto_id }}_"
|
||||
data-maxlength="{{ form.summary.field.max_length }}"></div>
|
||||
{{ form.name.errors|safe }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="addon-submission-field">
|
||||
<label>
|
||||
{{ _('Select 1 or 2 categories that best describe your add-on:') }}
|
||||
</label>
|
||||
{{ form.categories|safe }}
|
||||
{{ form.categories.errors|safe }}
|
||||
</div>
|
||||
<div class="addon-submission-field">
|
||||
<label>{{ _('Provide a more detailed description of your add-on:') }}</label>
|
||||
{{ form.description|safe }}
|
||||
{{ form.description.errors|safe }}
|
||||
<div class="edit-addon-details">
|
||||
{{ _("The description will appear on your add-on's detail page.") }}
|
||||
{{ some_html_tip() }}
|
||||
<div class="addon-submission-field">
|
||||
<label>{{ _("Your add-on's detail page will be:") }}</label>
|
||||
<div id="slug_edit" class="edit_with_prefix edit_initially_hidden">
|
||||
<span>{{ settings.SITE_URL }}</span>{{ form.slug|safe }}
|
||||
<div class="edit-addon-details">
|
||||
{{ _('Please use only letters, numbers, and dashes in your URL.') }}
|
||||
</div>
|
||||
</div>
|
||||
<span id="slug_readonly">
|
||||
{{ settings.SITE_URL }}/…/<span id="slug_value"></span>
|
||||
<a id="edit_slug" href="#">{{ _('Edit') }}</a>
|
||||
</span>
|
||||
{{ form.slug.errors|safe }}
|
||||
</div>
|
||||
<div class="addon-submission-field">
|
||||
<label>{{ _('Provide a brief summary of your add-on:') }}</label>
|
||||
{{ form.summary|safe }}
|
||||
{{ form.summary.errors|safe }}
|
||||
<div class="edit-addon-details">
|
||||
{% trans %}
|
||||
This summary will be shown with your add-on in listings and searches.
|
||||
{% endtrans %}
|
||||
<div class="char-count"
|
||||
data-for-startswith="{{ form.summary.auto_id }}_"
|
||||
data-maxlength="{{ form.summary.field.max_length }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="addon-submission-field"
|
||||
data-max-categories="{{ amo.MAX_CATEGORIES }}">
|
||||
{{ cat_form.non_form_errors()|safe }}
|
||||
{{ cat_form.management_form|safe }}
|
||||
{% for form in cat_form.initial_forms %}
|
||||
{{ select_cats(amo.MAX_CATEGORIES, form) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="addon-submission-field">
|
||||
<label>{{ _('Provide a more detailed description of your add-on:') }}</label>
|
||||
{{ form.description|safe }}
|
||||
{{ form.description.errors|safe }}
|
||||
<div class="edit-addon-details">
|
||||
{{ _("The description will appear on your add-on's detail page.") }}
|
||||
{{ some_html_tip() }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="submission-buttons addon-submission-field">
|
||||
<button type="submit">
|
||||
{{ _('Continue') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock primary %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% from "devhub/includes/macros.html" import tip, empty_unless, trans_readonly %}
|
||||
{% from "devhub/includes/macros.html" import tip, empty_unless, select_cats, trans_readonly %}
|
||||
|
||||
<form method="post" action="{{ url('devhub.addons.section', addon.slug, 'basic', 'edit') }}"
|
||||
id="addon-edit-basic" data-baseurl="{{ url('devhub.addons.edit', addon.slug) }}">
|
||||
|
@ -73,19 +73,25 @@
|
|||
Choose any that fit your add-on's functionality for the most
|
||||
exposure.")) }}
|
||||
</th>
|
||||
<td id="addon_categories_edit" data-max-categories="{{ amo.MAX_CATEGORIES }}">
|
||||
<td id="addon_categories_edit"
|
||||
data-max-categories="{{ amo.MAX_CATEGORIES }}">
|
||||
{% if editable %}
|
||||
<p>{{ ngettext('Select <b>up to {0}</b> category for this add-on.',
|
||||
'Select <b>up to {0}</b> categories for this add-on.',
|
||||
amo.MAX_CATEGORIES)|f(amo.MAX_CATEGORIES)|safe }}</p>
|
||||
{{ form.categories|safe }}
|
||||
{{ form.categories.errors|safe }}
|
||||
{% else %}
|
||||
{% set pipe = joiner("·") %}
|
||||
{% for category in categories %}
|
||||
{{ pipe()|safe }}
|
||||
{{ category }}
|
||||
{{ cat_form.non_form_errors()|safe }}
|
||||
{{ cat_form.management_form|safe }}
|
||||
{% for form in cat_form.initial_forms %}
|
||||
{{ select_cats(amo.MAX_CATEGORIES, form) }}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% set categories = addon.app_categories %}
|
||||
{% if categories %}
|
||||
<ul class="addon-app-categories">
|
||||
{% for app, cats in categories %}
|
||||
<li>
|
||||
<b>{{ app.pretty }}:</b> {{ cats|join(' · ')|safe }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -64,3 +64,16 @@
|
|||
</td>
|
||||
</tr>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro select_cats(max, form) %}
|
||||
<div class="select-addon-cats">
|
||||
{# L10n: {0} is the maximum number of add-on categories allowed.
|
||||
{1} is the application name. #}
|
||||
<label>{{ ngettext('Select <b>up to {0}</b> {1} category for this add-on:',
|
||||
'Select <b>up to {0}</b> {1} categories for this add-on:',
|
||||
max)|f(max, form.app.pretty)|safe }}</label>
|
||||
{{ form.application|safe }}
|
||||
{{ form.categories|safe }}
|
||||
{{ form.categories.errors|safe }}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
|
|
@ -983,6 +983,10 @@ class TestEdit(test_utils.TestCase):
|
|||
settings.PREVIEW_THUMBNAIL_PATH = tempfile.mkstemp()[1] + '%s/%d.png'
|
||||
settings.ADDON_ICONS_PATH = tempfile.mkdtemp()
|
||||
|
||||
self.basic_url = self.get_url('basic', True)
|
||||
ctx = self.client.get(self.basic_url).context['cat_form']
|
||||
self.cat_initial = initial(ctx.initial_forms[0])
|
||||
|
||||
def tearDown(self):
|
||||
reset_redis(self._redis)
|
||||
settings.PREVIEW_THUMBNAIL_PATH = self.old_settings['preview']
|
||||
|
@ -1009,7 +1013,6 @@ class TestEdit(test_utils.TestCase):
|
|||
args = [self.addon.slug, section]
|
||||
if edit:
|
||||
args.append('edit')
|
||||
|
||||
return reverse('devhub.addons.section', args=args)
|
||||
|
||||
def test_redirect(self):
|
||||
|
@ -1019,10 +1022,12 @@ class TestEdit(test_utils.TestCase):
|
|||
self.assertRedirects(r, url, 301)
|
||||
|
||||
def get_dict(self, **kw):
|
||||
fs = formset(self.cat_initial, initial_count=1)
|
||||
result = {'name': 'new name', 'slug': 'test_slug',
|
||||
'summary': 'new summary', 'categories': ['22'],
|
||||
'summary': 'new summary',
|
||||
'tags': ', '.join(self.tags)}
|
||||
result.update(**kw)
|
||||
result.update(fs)
|
||||
return result
|
||||
|
||||
def test_edit_basic(self):
|
||||
|
@ -1197,80 +1202,78 @@ class TestEdit(test_utils.TestCase):
|
|||
eq_(tag.tag_text, 'scriptalertfooscript')
|
||||
|
||||
def test_edit_basic_categories_add(self):
|
||||
eq_([c.id for c in self.get_addon().categories.all()], [22])
|
||||
data = self.get_dict(name='new name!', categories=[22, 23])
|
||||
self.client.post(self.get_url('basic', True), data)
|
||||
|
||||
categories = self.get_addon().categories.all()
|
||||
|
||||
eq_(categories[0].id, 22)
|
||||
eq_(categories[1].id, 23)
|
||||
eq_([c.id for c in self.get_addon().all_categories], [22])
|
||||
self.cat_initial['categories'] = [22, 23]
|
||||
r = self.client.post(self.basic_url, formset(self.cat_initial,
|
||||
initial_count=1))
|
||||
eq_([c.id for c in self.get_addon().all_categories], [22, 23])
|
||||
|
||||
def test_edit_basic_categories_addandremove(self):
|
||||
AddonCategory(addon=self.addon, category_id=23).save()
|
||||
eq_([c.id for c in self.get_addon().all_categories], [22, 23])
|
||||
|
||||
eq_([c.id for c in self.get_addon().categories.all()], [22, 23])
|
||||
data = self.get_dict(name='new name!', categories=[22, 24])
|
||||
self.client.post(self.get_url('basic', True), data)
|
||||
|
||||
category_ids_new = [c.id for c in self.get_addon().categories.all()]
|
||||
self.cat_initial['categories'] = [22, 24]
|
||||
r = self.client.post(self.basic_url, formset(self.cat_initial,
|
||||
initial_count=1))
|
||||
|
||||
category_ids_new = [c.id for c in self.get_addon().all_categories]
|
||||
eq_(category_ids_new, [22, 24])
|
||||
|
||||
def test_edit_basic_categories_xss(self):
|
||||
category_other = Category.objects.get(id=22)
|
||||
category_other.name = '<script>alert("test");</script>'
|
||||
category_other.save()
|
||||
data = self.get_dict(name='new name!', categories=[22, 24])
|
||||
r = self.client.post(self.get_url('basic', True), data)
|
||||
c = Category.objects.get(id=22)
|
||||
c.name = '<script>alert("test");</script>'
|
||||
c.save()
|
||||
|
||||
self.cat_initial['categories'] = [22, 24]
|
||||
r = self.client.post(self.basic_url, formset(self.cat_initial,
|
||||
initial_count=1))
|
||||
|
||||
assert '<script>alert' not in r.content
|
||||
assert '<script>alert' in r.content
|
||||
|
||||
def test_edit_basic_categories_remove(self):
|
||||
category = Category.objects.get(id=23)
|
||||
AddonCategory(addon=self.addon, category=category).save()
|
||||
c = Category.objects.get(id=23)
|
||||
AddonCategory(addon=self.addon, category=c).save()
|
||||
eq_([c.id for c in self.get_addon().all_categories], [22, 23])
|
||||
|
||||
eq_([c.id for c in self.get_addon().categories.all()], [22, 23])
|
||||
data = self.get_dict(name='new name!')
|
||||
self.client.post(self.get_url('basic', True), data)
|
||||
|
||||
category_ids_new = [c.id for c in self.get_addon().categories.all()]
|
||||
self.cat_initial['categories'] = [22]
|
||||
r = self.client.post(self.basic_url, formset(self.cat_initial,
|
||||
initial_count=1))
|
||||
|
||||
category_ids_new = [c.id for c in self.get_addon().all_categories]
|
||||
eq_(category_ids_new, [22])
|
||||
|
||||
def test_edit_basic_categories_required(self):
|
||||
data = self.get_dict(categories=[])
|
||||
r = self.client.post(self.get_url('basic', True), data)
|
||||
self.assertFormError(r, 'form', 'categories',
|
||||
'This field is required.')
|
||||
del self.cat_initial['categories']
|
||||
r = self.client.post(self.basic_url, formset(self.cat_initial,
|
||||
initial_count=1))
|
||||
eq_(r.context['cat_form'].errors[0]['categories'],
|
||||
['This field is required.'])
|
||||
|
||||
def test_edit_basic_categories_max(self):
|
||||
data = self.get_dict(categories=[22, 23, 24])
|
||||
r = self.client.post(self.get_url('basic', True), data)
|
||||
|
||||
error = 'You can only have 2 categories.'
|
||||
self.assertFormError(r, 'form', 'categories', error)
|
||||
eq_(amo.MAX_CATEGORIES, 2)
|
||||
self.cat_initial['categories'] = [22, 23, 24]
|
||||
r = self.client.post(self.basic_url, formset(self.cat_initial,
|
||||
initial_count=1))
|
||||
eq_(r.context['cat_form'].errors[0]['categories'],
|
||||
['You can have only 2 categories.'])
|
||||
|
||||
def test_edit_basic_categories_other_failure(self):
|
||||
category_other = Category.objects.get(id=22)
|
||||
category_other.name = 'Other'
|
||||
category_other.save()
|
||||
|
||||
data = self.get_dict(categories=[22, 23])
|
||||
r = self.client.post(self.get_url('basic', True), data)
|
||||
|
||||
error = ("The category 'Other' can not be combined with "
|
||||
"additional categories.")
|
||||
self.assertFormError(r, 'form', 'categories', error)
|
||||
Category.objects.get(id=22).update(misc=True)
|
||||
self.cat_initial['categories'] = [22, 23]
|
||||
r = self.client.post(self.basic_url, formset(self.cat_initial,
|
||||
initial_count=1))
|
||||
eq_(r.context['cat_form'].errors[0]['categories'],
|
||||
['The miscellaneous category cannot be combined with additional '
|
||||
'categories.'])
|
||||
|
||||
def test_edit_basic_categories_nonexistent(self):
|
||||
data = self.get_dict(categories=[100])
|
||||
r = self.client.post(self.get_url('basic', True), data)
|
||||
# Users will only get this if they're messing with the form, so a
|
||||
# human readable error isn't necessary.
|
||||
err = 'Select a valid choice. 100 is not one of the available choices.'
|
||||
self.assertFormError(r, 'form', 'categories', err)
|
||||
self.cat_initial['categories'] = [100]
|
||||
r = self.client.post(self.basic_url, formset(self.cat_initial,
|
||||
initial_count=1))
|
||||
eq_(r.context['cat_form'].errors[0]['categories'],
|
||||
['Select a valid choice. 100 is not one of the available '
|
||||
'choices.'])
|
||||
|
||||
def test_edit_basic_name_not_empty(self):
|
||||
data = self.get_dict(name='', slug=self.addon.slug,
|
||||
|
@ -2598,6 +2601,7 @@ class TestSubmitStep3(test_utils.TestCase):
|
|||
|
||||
def setUp(self):
|
||||
super(TestSubmitStep3, self).setUp()
|
||||
self.addon = self.get_addon()
|
||||
self.url = reverse('devhub.submit.3', args=['a3615'])
|
||||
assert self.client.login(username='del@icio.us', password='password')
|
||||
SubmitStep.objects.create(addon_id=3615, step=3)
|
||||
|
@ -2609,22 +2613,30 @@ class TestSubmitStep3(test_utils.TestCase):
|
|||
AddonCategory.objects.filter(addon=self.get_addon(),
|
||||
category=Category.objects.get(id=24)).delete()
|
||||
|
||||
ctx = self.client.get(self.url).context['cat_form']
|
||||
self.cat_initial = initial(ctx.initial_forms[0])
|
||||
|
||||
def get_addon(self):
|
||||
return Addon.objects.no_cache().get(id=3615)
|
||||
|
||||
def tearDown(self):
|
||||
reset_redis(self._redis)
|
||||
|
||||
def get_dict(self, **kw):
|
||||
cat_initial = kw.pop('cat_initial', self.cat_initial)
|
||||
fs = formset(cat_initial, initial_count=1)
|
||||
result = {'name': 'Test name', 'slug': 'testname',
|
||||
'description': 'desc', 'summary': 'Hello!'}
|
||||
result.update(**kw)
|
||||
result.update(fs)
|
||||
return result
|
||||
|
||||
def test_submit_success(self):
|
||||
r = self.client.get(self.url)
|
||||
eq_(r.status_code, 200)
|
||||
|
||||
# Post and be redirected.
|
||||
d = {'name': 'Test name',
|
||||
'slug': 'testname',
|
||||
'description': 'desc',
|
||||
'categories': ['22'],
|
||||
'summary': 'Hello!'}
|
||||
d = self.get_dict()
|
||||
r = self.client.post(self.url, d)
|
||||
eq_(r.status_code, 302)
|
||||
eq_(SubmitStep.objects.get(addon=3615).step, 4)
|
||||
|
@ -2641,38 +2653,39 @@ class TestSubmitStep3(test_utils.TestCase):
|
|||
|
||||
def test_submit_name_unique(self):
|
||||
# Make sure name is unique.
|
||||
r = self.client.post(self.url, {'name': 'Cooliris'})
|
||||
r = self.client.post(self.url, self.get_dict(name='Cooliris'))
|
||||
error = 'This add-on name is already in use. Please choose another.'
|
||||
self.assertFormError(r, 'form', 'name', error)
|
||||
|
||||
def test_submit_name_unique_strip(self):
|
||||
# Make sure we can't sneak in a name by adding a space or two.
|
||||
r = self.client.post(self.url, {'name': ' Cooliris '})
|
||||
r = self.client.post(self.url, self.get_dict(name=' Cooliris '))
|
||||
error = 'This add-on name is already in use. Please choose another.'
|
||||
self.assertFormError(r, 'form', 'name', error)
|
||||
|
||||
def test_submit_name_unique_case(self):
|
||||
# Make sure unique names aren't case sensitive.
|
||||
r = self.client.post(self.url, {'name': 'cooliris'})
|
||||
r = self.client.post(self.url, self.get_dict(name='cooliris'))
|
||||
error = 'This add-on name is already in use. Please choose another.'
|
||||
self.assertFormError(r, 'form', 'name', error)
|
||||
|
||||
def test_submit_name_required(self):
|
||||
# Make sure name is required.
|
||||
r = self.client.post(self.url, {'dummy': 'text'})
|
||||
r = self.client.post(self.url, self.get_dict(name=''))
|
||||
eq_(r.status_code, 200)
|
||||
self.assertFormError(r, 'form', 'name', 'This field is required.')
|
||||
|
||||
def test_submit_name_length(self):
|
||||
# Make sure the name isn't too long.
|
||||
r = self.client.post(self.url, {'name': 'a' * 51})
|
||||
d = self.get_dict(name='a' * 51)
|
||||
r = self.client.post(self.url, d)
|
||||
eq_(r.status_code, 200)
|
||||
error = 'Ensure this value has at most 50 characters (it has 51).'
|
||||
self.assertFormError(r, 'form', 'name', error)
|
||||
|
||||
def test_submit_slug_invalid(self):
|
||||
# Submit an invalid slug.
|
||||
d = dict(slug='slug!!! aksl23%%')
|
||||
d = self.get_dict(slug='slug!!! aksl23%%')
|
||||
r = self.client.post(self.url, d)
|
||||
eq_(r.status_code, 200)
|
||||
self.assertFormError(r, 'form', 'slug', "Enter a valid 'slug' " +
|
||||
|
@ -2680,82 +2693,61 @@ class TestSubmitStep3(test_utils.TestCase):
|
|||
|
||||
def test_submit_slug_required(self):
|
||||
# Make sure the slug is required.
|
||||
r = self.client.post(self.url, {'dummy': 'text'})
|
||||
r = self.client.post(self.url, self.get_dict(slug=''))
|
||||
eq_(r.status_code, 200)
|
||||
self.assertFormError(r, 'form', 'slug', 'This field is required.')
|
||||
|
||||
def test_submit_summary_required(self):
|
||||
# Make sure summary is required.
|
||||
r = self.client.post(self.url, {'dummy': 'text'})
|
||||
r = self.client.post(self.url, self.get_dict(summary=''))
|
||||
eq_(r.status_code, 200)
|
||||
self.assertFormError(r, 'form', 'summary', 'This field is required.')
|
||||
|
||||
def test_submit_summary_length(self):
|
||||
# Summary is too long.
|
||||
r = self.client.post(self.url, {'summary': 'a' * 251})
|
||||
r = self.client.post(self.url, self.get_dict(summary='a' * 251))
|
||||
eq_(r.status_code, 200)
|
||||
error = 'Ensure this value has at most 250 characters (it has 251).'
|
||||
self.assertFormError(r, 'form', 'summary', error)
|
||||
|
||||
def test_submit_categories_required(self):
|
||||
r = self.client.post(self.url, {'summary': 'Hello.', 'categories': []})
|
||||
self.assertFormError(r, 'form', 'categories',
|
||||
'This field is required.')
|
||||
del self.cat_initial['categories']
|
||||
r = self.client.post(self.url,
|
||||
self.get_dict(cat_initial=self.cat_initial))
|
||||
eq_(r.context['cat_form'].errors[0]['categories'],
|
||||
['This field is required.'])
|
||||
|
||||
def test_submit_categories_max(self):
|
||||
r = self.client.post(self.url, {'categories': ['22', '23', '24']})
|
||||
error = 'You can only have 2 categories.'
|
||||
self.assertFormError(r, 'form', 'categories', error)
|
||||
eq_(amo.MAX_CATEGORIES, 2)
|
||||
self.cat_initial['categories'] = [22, 23, 24]
|
||||
r = self.client.post(self.url,
|
||||
self.get_dict(cat_initial=self.cat_initial))
|
||||
eq_(r.context['cat_form'].errors[0]['categories'],
|
||||
['You can have only 2 categories.'])
|
||||
|
||||
def test_submit_categories_add(self):
|
||||
eq_([c.id for c in self.get_addon().categories.all()], [22])
|
||||
|
||||
data = dict(name='new name!',
|
||||
slug='test_slug',
|
||||
summary='new summary',
|
||||
categories=[22, 23],
|
||||
tags='ab, cd, ef')
|
||||
|
||||
self.client.post(self.url, data)
|
||||
|
||||
categories = self.get_addon().categories.all()
|
||||
|
||||
eq_(categories[0].id, 22)
|
||||
eq_(categories[1].id, 23)
|
||||
eq_([c.id for c in self.get_addon().all_categories], [22])
|
||||
self.cat_initial['categories'] = [22, 23]
|
||||
self.client.post(self.url, self.get_dict(cat_initial=self.cat_initial))
|
||||
eq_([c.id for c in self.get_addon().all_categories], [22, 23])
|
||||
|
||||
def test_submit_categories_addandremove(self):
|
||||
AddonCategory(addon=self.get_addon(), category_id=23).save()
|
||||
|
||||
eq_([c.id for c in self.get_addon().categories.all()], [22, 23])
|
||||
|
||||
data = dict(name='new name!',
|
||||
slug='test_slug',
|
||||
summary='new summary',
|
||||
categories=[22, 24],
|
||||
tags='ab, cd, ef')
|
||||
|
||||
self.client.post(self.url, data)
|
||||
|
||||
category_ids_new = [c.id for c in self.get_addon().categories.all()]
|
||||
AddonCategory(addon=self.addon, category_id=23).save()
|
||||
eq_([c.id for c in self.get_addon().all_categories], [22, 23])
|
||||
|
||||
self.cat_initial['categories'] = [22, 24]
|
||||
self.client.post(self.url, self.get_dict(cat_initial=self.cat_initial))
|
||||
category_ids_new = [c.id for c in self.get_addon().all_categories]
|
||||
eq_(category_ids_new, [22, 24])
|
||||
|
||||
def test_submit_categories_remove(self):
|
||||
category = Category.objects.get(id=23)
|
||||
AddonCategory(addon=self.get_addon(), category=category).save()
|
||||
|
||||
eq_([c.id for c in self.get_addon().categories.all()], [22, 23])
|
||||
|
||||
data = dict(name='new name!',
|
||||
slug='test_slug',
|
||||
summary='new summary',
|
||||
categories=[22],
|
||||
tags='ab, cd, ef')
|
||||
|
||||
self.client.post(self.url, data)
|
||||
|
||||
category_ids_new = [c.id for c in self.get_addon().categories.all()]
|
||||
c = Category.objects.get(id=23)
|
||||
AddonCategory(addon=self.addon, category=c).save()
|
||||
eq_([c.id for c in self.get_addon().all_categories], [22, 23])
|
||||
|
||||
self.cat_initial['categories'] = [22]
|
||||
self.client.post(self.url, self.get_dict(cat_initial=self.cat_initial))
|
||||
category_ids_new = [c.id for c in self.get_addon().all_categories]
|
||||
eq_(category_ids_new, [22])
|
||||
|
||||
def test_check_version(self):
|
||||
|
@ -2949,7 +2941,7 @@ class TestSubmitStep5(TestSubmitBase):
|
|||
def test_set_eula_nomsg(self):
|
||||
"""
|
||||
You should not get punished with a 500 for not writing your EULA...
|
||||
but perhaps you shoudl feel shame for lying to us. This test does not
|
||||
but perhaps you should feel shame for lying to us. This test does not
|
||||
test for shame.
|
||||
"""
|
||||
self.get_addon().update(eula=None, privacy_policy=None)
|
||||
|
|
|
@ -247,8 +247,7 @@ def edit(request, addon_id, addon):
|
|||
'page': 'edit',
|
||||
'addon': addon,
|
||||
'tags': addon.tags.not_blacklisted().values_list('tag_text', flat=True),
|
||||
'previews': addon.previews.all(),
|
||||
'categories': addon.categories.all()}
|
||||
'previews': addon.previews.all()}
|
||||
|
||||
return jingo.render(request, 'devhub/addons/edit.html', data)
|
||||
|
||||
|
@ -563,8 +562,14 @@ def addons_section(request, addon_id, addon, section, editable=False):
|
|||
if section not in models:
|
||||
return http.HttpResponseNotFound()
|
||||
|
||||
previews = []
|
||||
if section == 'media':
|
||||
tags = previews = []
|
||||
cat_form = None
|
||||
|
||||
if section == 'basic':
|
||||
tags = addon.tags.not_blacklisted().values_list('tag_text', flat=True)
|
||||
cat_form = addon_forms.CategoryFormSet(request.POST or None,
|
||||
addon=addon)
|
||||
elif section == 'media':
|
||||
previews = forms.PreviewFormSet(request.POST or None,
|
||||
prefix='files', queryset=addon.previews.all())
|
||||
|
||||
|
@ -572,7 +577,9 @@ def addons_section(request, addon_id, addon, section, editable=False):
|
|||
if request.method == 'POST':
|
||||
form = models[section](request.POST, request.FILES,
|
||||
instance=addon, request=request)
|
||||
if form.is_valid() and (not previews or previews.is_valid()):
|
||||
|
||||
if (form.is_valid() and (not previews or previews.is_valid()) and
|
||||
(cat_form and cat_form.is_valid())):
|
||||
addon = form.save(addon)
|
||||
|
||||
if previews:
|
||||
|
@ -584,23 +591,19 @@ def addons_section(request, addon_id, addon, section, editable=False):
|
|||
amo.log(amo.LOG.CHANGE_ICON, addon)
|
||||
else:
|
||||
amo.log(amo.LOG.EDIT_PROPERTIES, addon)
|
||||
|
||||
if cat_form:
|
||||
cat_form.save()
|
||||
else:
|
||||
form = models[section](instance=addon, request=request)
|
||||
else:
|
||||
form = False
|
||||
|
||||
tags = []
|
||||
categories = []
|
||||
|
||||
if section == 'basic':
|
||||
tags = addon.tags.not_blacklisted().values_list('tag_text', flat=True)
|
||||
categories = addon.categories.all()
|
||||
|
||||
data = {'addon': addon,
|
||||
'form': form,
|
||||
'editable': editable,
|
||||
'categories': categories,
|
||||
'tags': tags,
|
||||
'cat_form': cat_form,
|
||||
'preview_form': previews}
|
||||
|
||||
return jingo.render(request,
|
||||
|
@ -827,13 +830,15 @@ def submit_addon(request, step):
|
|||
def submit_describe(request, addon_id, addon, step):
|
||||
form = forms.Step3Form(request.POST or None, instance=addon,
|
||||
request=request)
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
cat_form = addon_forms.CategoryFormSet(request.POST or None, addon=addon)
|
||||
if request.method == 'POST' and form.is_valid() and cat_form.is_valid():
|
||||
addon = form.save(addon)
|
||||
cat_form.save()
|
||||
SubmitStep.objects.filter(addon=addon).update(step=4)
|
||||
return redirect('devhub.submit.4', addon.slug)
|
||||
|
||||
return jingo.render(request, 'devhub/addons/submit/describe.html',
|
||||
{'form': form, 'addon': addon, 'step': step})
|
||||
{'form': form, 'cat_form': cat_form, 'addon': addon,
|
||||
'step': step})
|
||||
|
||||
|
||||
@dev_required
|
||||
|
|
|
@ -258,11 +258,6 @@ label.above-the-field .locale {
|
|||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
#addon_categories_edit ul {
|
||||
float: left;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
/*
|
||||
Bug 622030- TODO (potch) fix this later
|
||||
|
||||
|
@ -294,6 +289,55 @@ form .char-count b {
|
|||
width: 16px;
|
||||
}
|
||||
|
||||
/* @group Add-on category selection */
|
||||
|
||||
.select-addon-cats {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
#edit-addon-basic .select-addon-cats {
|
||||
border-top: 1px dotted #add0dc;
|
||||
margin: 8px 0 0;
|
||||
padding-top: 8px;
|
||||
}
|
||||
#edit-addon-basic input + .select-addon-cats {
|
||||
border-top-width: 0;
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.addon-categories {
|
||||
-moz-column-count: 3;
|
||||
-webkit-column-count: 3;
|
||||
column-count: 3;
|
||||
-moz-column-gap: 1.5em;
|
||||
-webkit-column-gap: 1.5em;
|
||||
column-gap: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
#addon_categories_edit .addon-categories {
|
||||
-moz-column-count: 2;
|
||||
-webkit-column-count: 2;
|
||||
column-count: 2;
|
||||
}
|
||||
|
||||
.addon-misc-category,
|
||||
.addon-app-categories {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.addon-categories label,
|
||||
.addon-misc-category label {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.addon-app-categories li b {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
/* @end */
|
||||
|
||||
#file-list .preview {
|
||||
overflow: auto;
|
||||
margin-bottom: 15px;
|
||||
|
@ -770,7 +814,8 @@ h3 a.subscribe-feed:hover {
|
|||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.addon-submission-process form label {
|
||||
.addon-submission-process form label,
|
||||
#edit-addon-basic .select-addon-cats label {
|
||||
display: block;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
|
|
@ -44,6 +44,11 @@ $(document).ready(function() {
|
|||
initUploadControls();
|
||||
}
|
||||
|
||||
// Submission > Describe
|
||||
if ($("#submit-describe").length) {
|
||||
initCatFields();
|
||||
}
|
||||
|
||||
// Submission > Media
|
||||
if($('#submit-media').length) {
|
||||
initUploadIcon();
|
||||
|
@ -241,29 +246,6 @@ $("#user-form-template .email-autocomplete")
|
|||
function initEditAddon() {
|
||||
if (z.noEdit) return;
|
||||
|
||||
// Manage checked/unchecked categories.
|
||||
var max_categories = parseInt($('#addon_categories_edit').attr('data-max-categories'));
|
||||
|
||||
var manage_checked_all = function() {
|
||||
var p = $('#addon_categories_edit'),
|
||||
checked_length = $('ul:not(.other) input:checked', p).length,
|
||||
disabled = checked_length >= max_categories;
|
||||
|
||||
$('ul.other input', p).attr('checked', checked_length <= 0);
|
||||
$('ul:not(.other) input:not(:checked)', p).attr('disabled', disabled)
|
||||
};
|
||||
|
||||
var manage_checked_other = function() {
|
||||
$('#addon_categories_edit ul:not(.other) input').attr('checked', false);
|
||||
$('#addon_categories_edit ul:not(.other) input').attr('disabled', false);
|
||||
};
|
||||
|
||||
$('#edit-addon').delegate('#addon_categories_edit ul:not(.other) input',
|
||||
'change', manage_checked_all);
|
||||
|
||||
$('#edit-addon').delegate('#addon_categories_edit ul.other input',
|
||||
'change', manage_checked_other);
|
||||
|
||||
// Load the edit form.
|
||||
$('#edit-addon').delegate('h3 a', 'click', function(e){
|
||||
e.preventDefault();
|
||||
|
@ -274,7 +256,7 @@ function initEditAddon() {
|
|||
(function(parent_div, a){
|
||||
parent_div.load($(a).attr('data-editurl'), function(){
|
||||
if($('#addon_categories_edit').length) {
|
||||
manage_checked_all();
|
||||
initCatFields();
|
||||
}
|
||||
$(this).each(addonFormSubmit);
|
||||
});
|
||||
|
@ -416,7 +398,7 @@ function initUploadIcon() {
|
|||
$('#icons_default a.active').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
|
||||
$("#id_icon_upload").val("")
|
||||
$("#id_icon_upload").val("");
|
||||
|
||||
$('#icon_preview_32 img').attr('src', $('img', $parent).attr('src'));
|
||||
$('#icon_preview_64 img').attr('src', $('img',
|
||||
|
@ -431,7 +413,7 @@ function initUploadIcon() {
|
|||
$('#icons_default input:checked').attr('checked', false);
|
||||
|
||||
$('input[name=icon_type][value='+file.type+']', $('#icons_default'))
|
||||
.attr('checked', true)
|
||||
.attr('checked', true);
|
||||
|
||||
$('#icons_default a.active').removeClass('active');
|
||||
$('#icon_preview img').attr('src', file.getAsDataURL());
|
||||
|
@ -899,6 +881,26 @@ function initPayments() {
|
|||
}).change();
|
||||
}
|
||||
|
||||
function initCatFields() {
|
||||
$(".select-addon-cats").each(function() {
|
||||
var $parent = $(this).closest("[data-max-categories]"),
|
||||
$main = $(this).find(".addon-categories"),
|
||||
$misc = $(this).find(".addon-misc-category"),
|
||||
maxCats = parseInt($parent.attr("data-max-categories"));
|
||||
var checkMain = function() {
|
||||
var checkedLength = $("input:checked", $main).length,
|
||||
disabled = checkedLength >= maxCats;
|
||||
$("input", $misc).attr("checked", checkedLength <= 0);
|
||||
$("input:not(:checked)", $main).attr("disabled", disabled);
|
||||
};
|
||||
var checkOther = function() {
|
||||
$("input", $main).attr("checked", false).attr("disabled", false);
|
||||
};
|
||||
$("input", $main).live("change", checkMain).trigger("change");
|
||||
$("input", $misc).live("change", checkOther);
|
||||
});
|
||||
}
|
||||
|
||||
function initLicenseFields() {
|
||||
$("#id_has_eula").change(function (e) {
|
||||
if ($(this).attr("checked")) {
|
||||
|
@ -1182,7 +1184,7 @@ function hideSameSizedIcons() {
|
|||
if($.inArray(size, icon_sizes) >= 0) {
|
||||
$(this).hide();
|
||||
}
|
||||
icon_sizes.push(size)
|
||||
icon_sizes.push(size);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
ALTER TABLE categories
|
||||
ADD COLUMN misc tinyint(1) UNSIGNED NOT NULL DEFAULT '0';
|
||||
|
||||
UPDATE categories SET misc=1 WHERE slug IN ('miscellaneous', 'other');
|
Загрузка…
Ссылка в новой задаче