add ability to edit themes (bug 679059, bug 848038)
This commit is contained in:
Родитель
1dcb320a19
Коммит
985f6b52f9
|
@ -129,6 +129,12 @@ class AddonFormBase(TranslationFormMixin, happyforms.ModelForm):
|
|||
def clean_tags(self):
|
||||
return clean_tags(self.request, self.cleaned_data['tags'])
|
||||
|
||||
def get_tags(self, addon):
|
||||
if acl.action_allowed(self.request, 'Addons', 'Edit'):
|
||||
return [t.tag_text for t in addon.tags.all()]
|
||||
else:
|
||||
return [t.tag_text for t in addon.tags.filter(restricted=False)]
|
||||
|
||||
|
||||
class AddonFormBasic(AddonFormBase):
|
||||
name = TransField(max_length=50)
|
||||
|
@ -149,10 +155,6 @@ class AddonFormBasic(AddonFormBase):
|
|||
|
||||
super(AddonFormBasic, self).__init__(*args, **kw)
|
||||
|
||||
# Theme summary optional.
|
||||
if kw['instance'].is_persona():
|
||||
self.fields['summary'].required = False
|
||||
|
||||
self.fields['tags'].initial = ', '.join(self.get_tags(self.instance))
|
||||
# Do not simply append validators, as validators will persist between
|
||||
# instances.
|
||||
|
@ -161,12 +163,6 @@ class AddonFormBasic(AddonFormBase):
|
|||
name_validators.append(validate_name)
|
||||
self.fields['name'].validators = name_validators
|
||||
|
||||
def get_tags(self, addon):
|
||||
if acl.action_allowed(self.request, 'Addons', 'Edit'):
|
||||
return [t.tag_text for t in addon.tags.all()]
|
||||
else:
|
||||
return [t.tag_text for t in addon.tags.filter(restricted=False)]
|
||||
|
||||
def save(self, addon, commit=False):
|
||||
tags_new = self.cleaned_data['tags']
|
||||
tags_old = [slugify(t, spaces=True) for t in self.get_tags(addon)]
|
||||
|
@ -490,7 +486,29 @@ class AbuseForm(happyforms.Form):
|
|||
del self.fields['recaptcha']
|
||||
|
||||
|
||||
class NewPersonaForm(AddonFormBase):
|
||||
class ThemeFormBase(AddonFormBase):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ThemeFormBase, self).__init__(*args, **kwargs)
|
||||
cats = Category.objects.filter(type=amo.ADDON_PERSONA, weight__gte=0)
|
||||
cats = sorted(cats, key=lambda x: x.name)
|
||||
self.fields['category'].choices = [(c.id, c.name) for c in cats]
|
||||
|
||||
for field in ('header', 'footer'):
|
||||
self.fields[field].widget.attrs = {
|
||||
'data-upload-url': reverse('devhub.personas.upload_persona',
|
||||
args=['persona_%s' % field]),
|
||||
'data-allowed-types': 'image/jpeg|image/png'
|
||||
}
|
||||
|
||||
def clean_name(self):
|
||||
return clean_name(self.cleaned_data['name'])
|
||||
|
||||
def clean_slug(self):
|
||||
return clean_slug(self.cleaned_data['slug'], self.instance)
|
||||
|
||||
|
||||
class ThemeForm(ThemeFormBase):
|
||||
name = forms.CharField(max_length=50)
|
||||
slug = forms.CharField(max_length=30)
|
||||
category = forms.ModelChoiceField(queryset=Category.objects.all(),
|
||||
|
@ -517,25 +535,6 @@ class NewPersonaForm(AddonFormBase):
|
|||
model = Addon
|
||||
fields = ('name', 'slug', 'summary', 'tags')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NewPersonaForm, self).__init__(*args, **kwargs)
|
||||
cats = Category.objects.filter(type=amo.ADDON_PERSONA, weight__gte=0)
|
||||
cats = sorted(cats, key=lambda x: x.name)
|
||||
self.fields['category'].choices = [(c.id, c.name) for c in cats]
|
||||
|
||||
for field in ('header', 'footer'):
|
||||
self.fields[field].widget.attrs = {
|
||||
'data-upload-url': reverse('devhub.personas.upload_persona',
|
||||
args=['persona_%s' % field]),
|
||||
'data-allowed-types': 'image/jpeg|image/png'
|
||||
}
|
||||
|
||||
def clean_name(self):
|
||||
return clean_name(self.cleaned_data['name'])
|
||||
|
||||
def clean_slug(self):
|
||||
return clean_slug(self.cleaned_data['slug'], self.instance)
|
||||
|
||||
def save(self, commit=False):
|
||||
from addons.tasks import (create_persona_preview_images,
|
||||
save_persona_image)
|
||||
|
@ -598,6 +597,106 @@ class NewPersonaForm(AddonFormBase):
|
|||
return addon
|
||||
|
||||
|
||||
class EditThemeForm(AddonFormBase):
|
||||
name = forms.CharField(max_length=50)
|
||||
slug = forms.CharField(max_length=30)
|
||||
category = forms.ModelChoiceField(queryset=Category.objects.all(),
|
||||
widget=forms.widgets.RadioSelect)
|
||||
summary = forms.CharField(widget=forms.Textarea(attrs={'rows': 4}),
|
||||
max_length=250, required=False)
|
||||
tags = forms.CharField(required=False)
|
||||
accentcolor = ColorField(required=False)
|
||||
textcolor = ColorField(required=False)
|
||||
license = forms.TypedChoiceField(choices=amo.PERSONA_LICENSES_IDS,
|
||||
coerce=int, empty_value=None, widget=forms.HiddenInput,
|
||||
error_messages={'required': _lazy(u'A license must be selected.')})
|
||||
|
||||
class Meta:
|
||||
model = Addon
|
||||
fields = ('name', 'slug', 'summary', 'tags')
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
self.request = kw.pop('request')
|
||||
|
||||
super(AddonFormBase, self).__init__(*args, **kw)
|
||||
|
||||
addon = self.instance
|
||||
persona = addon.persona
|
||||
|
||||
# Do not simply append validators, as validators will persist between
|
||||
# instances.
|
||||
self.fields['name'].validators = list(self.fields['name'].validators)
|
||||
self.fields['name'].validators.append(lambda x: clean_name(x, addon))
|
||||
|
||||
self.initial['tags'] = ', '.join(self.get_tags(addon))
|
||||
if persona.accentcolor:
|
||||
self.initial['accentcolor'] = '#' + persona.accentcolor
|
||||
if persona.textcolor:
|
||||
self.initial['textcolor'] = '#' + persona.textcolor
|
||||
self.initial['license'] = persona.license_id
|
||||
|
||||
cats = sorted(Category.objects.filter(type=amo.ADDON_PERSONA,
|
||||
weight__gte=0),
|
||||
key=lambda x: x.name)
|
||||
self.fields['category'].choices = [(c.id, c.name) for c in cats]
|
||||
try:
|
||||
self.initial['category'] = addon.categories.values_list(
|
||||
'id', flat=True)[0]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
def save(self):
|
||||
addon = self.instance
|
||||
persona = addon.persona
|
||||
data = self.cleaned_data
|
||||
|
||||
# Update Persona-specific data.
|
||||
persona_data = {
|
||||
'license_id': int(data['license']),
|
||||
'accentcolor': data['accentcolor'].lstrip('#'),
|
||||
'textcolor': data['textcolor'].lstrip('#'),
|
||||
'display_username': self.request.amo_user.username
|
||||
}
|
||||
changed = False
|
||||
for k, v in persona_data.iteritems():
|
||||
if v != getattr(persona, k):
|
||||
changed = True
|
||||
setattr(persona, k, v)
|
||||
if changed:
|
||||
persona.save()
|
||||
|
||||
# Update name, slug, and summary.
|
||||
if self.changed_data:
|
||||
amo.log(amo.LOG.EDIT_PROPERTIES, addon)
|
||||
|
||||
# Save the Addon object.
|
||||
super(EditThemeForm, self).save()
|
||||
|
||||
tags_new = data['tags']
|
||||
tags_old = [slugify(t, spaces=True) for t in self.get_tags(addon)]
|
||||
|
||||
# Add new tags.
|
||||
for t in set(tags_new) - set(tags_old):
|
||||
Tag(tag_text=t).save_tag(addon)
|
||||
|
||||
# Remove old tags.
|
||||
for t in set(tags_old) - set(tags_new):
|
||||
Tag(tag_text=t).remove_tag(addon)
|
||||
|
||||
# Update category.
|
||||
try:
|
||||
old_cat = (addon.addoncategory_set
|
||||
.exclude(category_id=data['category'].id))[0]
|
||||
except IndexError:
|
||||
# This should never happen, but maybe a category got deleted.
|
||||
addon.addoncategory_set.create(category=data['category'])
|
||||
else:
|
||||
old_cat.category = data['category']
|
||||
old_cat.save()
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class ThemeLicenseForm(happyforms.Form):
|
||||
license = forms.TypedChoiceField(choices=amo.PERSONA_LICENSES_IDS,
|
||||
coerce=int, empty_value=None, widget=forms.HiddenInput,
|
||||
|
|
|
@ -485,9 +485,12 @@ class Addon(amo.models.OnChangeMixin, amo.models.ModelBase):
|
|||
return reverse(view_name % (prefix, action),
|
||||
args=[self.app_slug] + args)
|
||||
else:
|
||||
view_name = '%s.%s' if prefix_only else '%s.addons.%s'
|
||||
return reverse(view_name % (prefix, action),
|
||||
args=[self.slug] + args)
|
||||
type_ = 'themes' if self.type == amo.ADDON_PERSONA else 'addons'
|
||||
if not prefix_only:
|
||||
prefix += '.%s' % type_
|
||||
view_name = '{prefix}.{action}'.format(prefix=prefix,
|
||||
action=action)
|
||||
return reverse(view_name, args=[self.slug] + args)
|
||||
|
||||
def get_detail_url(self, action='detail', args=[]):
|
||||
if self.is_webapp():
|
||||
|
@ -609,7 +612,10 @@ class Addon(amo.models.OnChangeMixin, amo.models.ModelBase):
|
|||
def latest_version(self):
|
||||
"""Returns the absolutely newest non-beta version. """
|
||||
if self.type == amo.ADDON_PERSONA:
|
||||
return
|
||||
try:
|
||||
return self.versions.all()[0]
|
||||
except IndexError:
|
||||
return
|
||||
if not self._latest_version:
|
||||
try:
|
||||
v = (self.versions.exclude(files__status=amo.STATUS_BETA)
|
||||
|
@ -748,8 +754,6 @@ class Addon(amo.models.OnChangeMixin, amo.models.ModelBase):
|
|||
@property
|
||||
def current_version(self):
|
||||
"Returns the current_version field or updates it if needed."
|
||||
if self.type == amo.ADDON_PERSONA:
|
||||
return
|
||||
try:
|
||||
if not self._current_version:
|
||||
self.update_version()
|
||||
|
|
|
@ -178,9 +178,9 @@ def persona_detail(request, addon, template=None):
|
|||
persona = addon.persona
|
||||
|
||||
# this persona's categories
|
||||
categories = addon.categories.filter(application=request.APP.id)
|
||||
categories = addon.categories.all()
|
||||
category_personas = None
|
||||
if categories:
|
||||
if categories.exists():
|
||||
qs = Addon.objects.public().filter(categories=categories[0])
|
||||
category_personas = _category_personas(qs, limit=6)
|
||||
|
||||
|
@ -188,7 +188,7 @@ def persona_detail(request, addon, template=None):
|
|||
'addon': addon,
|
||||
'persona': persona,
|
||||
'categories': categories,
|
||||
'author_personas': persona.authors_other_addons(request.APP)[:3],
|
||||
'author_personas': persona.authors_other_addons()[:3],
|
||||
'category_personas': category_personas,
|
||||
}
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ def global_settings(request):
|
|||
if waffle.flag_is_active(request, 'submit-personas'):
|
||||
# TODO(cvan)(fligtar): Do we want this here?
|
||||
tools_links.append({'text': _('Submit a New Theme'),
|
||||
'href': reverse('devhub.personas.submit')})
|
||||
'href': reverse('devhub.themes.submit')})
|
||||
|
||||
tools_links.append({'text': _('Developer Hub'),
|
||||
'href': reverse('devhub.index')})
|
||||
|
|
|
@ -84,7 +84,7 @@ class TestCommon(amo.tests.TestCase):
|
|||
expected = [
|
||||
('Tools', '#'),
|
||||
('Submit a New Add-on', reverse('devhub.submit.1')),
|
||||
('Submit a New Theme', reverse('devhub.personas.submit')),
|
||||
('Submit a New Theme', reverse('devhub.themes.submit')),
|
||||
('Developer Hub', reverse('devhub.index')),
|
||||
]
|
||||
check_links(expected, pq(r.content)('#aux-nav .tools a'))
|
||||
|
@ -104,7 +104,7 @@ class TestCommon(amo.tests.TestCase):
|
|||
('Tools', '#'),
|
||||
('Manage My Add-ons', reverse('devhub.addons')),
|
||||
('Submit a New Add-on', reverse('devhub.submit.1')),
|
||||
('Submit a New Theme', reverse('devhub.personas.submit')),
|
||||
('Submit a New Theme', reverse('devhub.themes.submit')),
|
||||
('Developer Hub', reverse('devhub.index')),
|
||||
]
|
||||
check_links(expected, pq(r.content)('#aux-nav .tools a'))
|
||||
|
@ -119,7 +119,7 @@ class TestCommon(amo.tests.TestCase):
|
|||
expected = [
|
||||
('Tools', '#'),
|
||||
('Submit a New Add-on', reverse('devhub.submit.1')),
|
||||
('Submit a New Theme', reverse('devhub.personas.submit')),
|
||||
('Submit a New Theme', reverse('devhub.themes.submit')),
|
||||
('Developer Hub', reverse('devhub.index')),
|
||||
('Editor Tools', reverse('editors.home')),
|
||||
]
|
||||
|
@ -139,7 +139,7 @@ class TestCommon(amo.tests.TestCase):
|
|||
('Tools', '#'),
|
||||
('Manage My Add-ons', reverse('devhub.addons')),
|
||||
('Submit a New Add-on', reverse('devhub.submit.1')),
|
||||
('Submit a New Theme', reverse('devhub.personas.submit')),
|
||||
('Submit a New Theme', reverse('devhub.themes.submit')),
|
||||
('Developer Hub', reverse('devhub.index')),
|
||||
('Editor Tools', reverse('editors.home')),
|
||||
]
|
||||
|
@ -157,7 +157,7 @@ class TestCommon(amo.tests.TestCase):
|
|||
expected = [
|
||||
('Tools', '#'),
|
||||
('Submit a New Add-on', reverse('devhub.submit.1')),
|
||||
('Submit a New Theme', reverse('devhub.personas.submit')),
|
||||
('Submit a New Theme', reverse('devhub.themes.submit')),
|
||||
('Developer Hub', reverse('devhub.index')),
|
||||
('Editor Tools', reverse('editors.home')),
|
||||
('Localizer Tools', '/localizers'),
|
||||
|
@ -181,7 +181,7 @@ class TestCommon(amo.tests.TestCase):
|
|||
('Tools', '#'),
|
||||
('Manage My Add-ons', reverse('devhub.addons')),
|
||||
('Submit a New Add-on', reverse('devhub.submit.1')),
|
||||
('Submit a New Theme', reverse('devhub.personas.submit')),
|
||||
('Submit a New Theme', reverse('devhub.themes.submit')),
|
||||
('Developer Hub', reverse('devhub.index')),
|
||||
('Editor Tools', reverse('editors.home')),
|
||||
('Localizer Tools', '/localizers'),
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
Your browser, your style! Dress it up with a design of your own!
|
||||
{% endtrans %}
|
||||
</p>
|
||||
<p><a href="{{ url('devhub.personas.submit') }}" class="button">{{ _('Submit a New Theme') }}</a></p>
|
||||
<p><a href="{{ url('devhub.themes.submit') }}" class="button">{{ _('Submit a New Theme') }}</a></p>
|
||||
<p><a href="{{ url('devhub.docs', 'themes') }}" class="learn-more">{{ _('Learn more') }}</a></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -52,13 +52,13 @@
|
|||
</div>
|
||||
{% else %}
|
||||
<section class="secondary devhub-sidebar">
|
||||
<p id="submit-addon" class="cta">
|
||||
<p id="submit-addon" class="submit-cta">
|
||||
<a href="{{ url('devhub.submit.1') }}"
|
||||
class="button prominent">{{ _('Submit a New Add-on') }}</a>
|
||||
</p>
|
||||
{% if waffle.flag('submit-personas') %}
|
||||
<p class="submit-theme cta">
|
||||
<a href="{{ url('devhub.personas.submit') }}"
|
||||
<p class="submit-theme submit-cta">
|
||||
<a href="{{ url('devhub.themes.submit') }}"
|
||||
class="button prominent">{{ _('Submit a New Theme') }}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
|
|
@ -29,33 +29,27 @@
|
|||
{% include 'devhub/addons/edit/basic.html' %}
|
||||
</div>
|
||||
|
||||
{% if theme %}
|
||||
<div class="edit-addon-section" id="edit-addon-license">
|
||||
{% include 'devhub/addons/edit/license.html' %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="edit-addon-section" id="edit-addon-media"
|
||||
data-checkurl="{{ url('devhub.ajax.image.status', addon.slug) }}">
|
||||
{% include 'devhub/addons/edit/media.html' %}
|
||||
</div>
|
||||
<div class="edit-addon-section" id="edit-addon-media"
|
||||
data-checkurl="{{ url('devhub.ajax.image.status', addon.slug) }}">
|
||||
{% include 'devhub/addons/edit/media.html' %}
|
||||
</div>
|
||||
|
||||
<div class="edit-addon-section" id="edit-addon-details">
|
||||
{% include 'devhub/addons/edit/details.html' %}
|
||||
</div>
|
||||
<div class="edit-addon-section" id="edit-addon-details">
|
||||
{% include 'devhub/addons/edit/details.html' %}
|
||||
</div>
|
||||
|
||||
<div class="edit-addon-section" id="edit-addon-support">
|
||||
{% include 'devhub/addons/edit/support.html' %}
|
||||
</div>
|
||||
<div class="edit-addon-section" id="edit-addon-support">
|
||||
{% include 'devhub/addons/edit/support.html' %}
|
||||
</div>
|
||||
|
||||
<div class="edit-addon-section" id="edit-addon-technical">
|
||||
{% include 'devhub/addons/edit/technical.html' %}
|
||||
</div>
|
||||
<div class="edit-addon-section" id="edit-addon-technical">
|
||||
{% include 'devhub/addons/edit/technical.html' %}
|
||||
</div>
|
||||
|
||||
{% if admin_form %}
|
||||
<div class="edit-addon-section" id="edit-addon-admin">
|
||||
{% include 'devhub/addons/edit/admin.html' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if admin_form %}
|
||||
<div class="edit-addon-section" id="edit-addon-admin">
|
||||
{% include 'devhub/addons/edit/admin.html' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
|
|
@ -34,13 +34,8 @@
|
|||
{{ tip('App URL',
|
||||
'Choose a short, unique URL slug for your app.') }}
|
||||
{% else %}
|
||||
{% if theme %}
|
||||
{{ tip(_("Theme URL"),
|
||||
_("Choose a short, unique URL slug for your theme.")) }}
|
||||
{% else %}
|
||||
{{ tip(_("Add-on URL"),
|
||||
_("Choose a short, unique URL slug for your add-on.")) }}
|
||||
{% endif %}
|
||||
{{ tip(_("Add-on URL"),
|
||||
_("Choose a short, unique URL slug for your add-on.")) }}
|
||||
{% endif %}
|
||||
</th>
|
||||
<td id="slug_edit">
|
||||
|
@ -133,7 +128,7 @@
|
|||
{% endif %}
|
||||
</p>
|
||||
{% else %}
|
||||
{{ select_cats(amo.MAX_CATEGORIES, form, webapp, theme) }}
|
||||
{{ select_cats(amo.MAX_CATEGORIES, form, webapp) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
{% set title = _('Delete Theme') if addon.is_persona() else _('Delete Add-on') %}
|
||||
<form method="post" action="{{ addon.get_dev_url('delete') }}">
|
||||
{{ csrf() }}
|
||||
<h3>
|
||||
{% if webapp %}
|
||||
{{ loc('Delete App') }}
|
||||
{% else %}
|
||||
{{ _('Delete Add-on') }}
|
||||
{% endif %}
|
||||
{{ title }}
|
||||
</h3>
|
||||
{% if addon.highest_status or addon.status %}
|
||||
<p class="warning">
|
||||
{% if webapp %}
|
||||
{{ loc('Deleting your app will permanently remove it from the site.') }}
|
||||
{% elif addon.is_persona() %}
|
||||
{{ _('Deleting your theme will permanently remove it from the site.') }}
|
||||
{% else %}
|
||||
{% trans %}
|
||||
Deleting your add-on will permanently remove it from the site and
|
||||
|
@ -30,8 +29,7 @@
|
|||
</p>
|
||||
<input type="hidden" name="addon_id" class="addon_id" value="{{ addon.id }}">
|
||||
<p>
|
||||
<button class="delete-button" type="submit">
|
||||
{{ loc('Delete App') if webapp else _('Delete Add-on') }}</button>
|
||||
<button class="delete-button" type="submit">{{ title }}</button>
|
||||
{{ _('or') }} <a href="#" class="close cancel">{{ _('Cancel') }}</a>
|
||||
</p>
|
||||
<a href="#" class="close">{{ _('Close') }}</a>
|
||||
|
|
|
@ -1,6 +1,23 @@
|
|||
<h5>{{ _('Actions') }}</h5>
|
||||
<ul>
|
||||
{% if addon.is_incomplete() %}
|
||||
{% if check_addon_ownership(request, addon, dev=True) %}
|
||||
<li>
|
||||
<a href="{{ addon.get_dev_url() }}" class="tooltip"
|
||||
title="{{ _("Edit details about this theme's listing.") }}">
|
||||
{{ _('Edit Listing') }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if addon.is_public() %}
|
||||
<li>
|
||||
<a href="{{ addon.get_url_path() }}" class="tooltip"
|
||||
title="{{ _("View this theme's public listing page.") }}">
|
||||
{{ _('View Listing') }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="{{ url('devhub.feed', addon.slug) }}">
|
||||
{{ _('View Recent Changes') }}</a>
|
||||
</li>
|
||||
{% if check_addon_ownership(request, addon) and addon.can_be_deleted() %}
|
||||
<li>
|
||||
<a href="#" class="delete-addon tooltip"
|
||||
|
@ -10,25 +27,4 @@
|
|||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if check_addon_ownership(request, addon, dev=True) %}
|
||||
<li>
|
||||
<a href="{{ addon.get_dev_url() }}" class="tooltip"
|
||||
title="{{ _("Edit details about this theme's listing.") }}">
|
||||
{{ _('Edit Listing') }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="#" class="more-actions">{{ _('More') }}</a>
|
||||
<div class="more-actions-popup popup hidden">
|
||||
{% set view_urls = ((addon.get_url_path(), _('View Add-on Listing')),
|
||||
(url('devhub.feed', addon.slug), _('View Recent Changes'))) %}
|
||||
<ul>
|
||||
{% for url, title in view_urls %}
|
||||
<li><a href="{{ url }}">{{ title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<a href="{{ url('devhub.docs', 'themes') }}">{{ _('How to create a theme') }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url('devhub.personas.submit') }}">{{ _('Submit your theme') }}</a>
|
||||
<a href="{{ url('devhub.themes.submit') }}">{{ _('Submit your theme') }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url('devhub.docs', 'themes', 'faq') }}">{{ _('Frequently Asked Questions') }}</a>
|
||||
|
@ -38,7 +38,7 @@
|
|||
{% endtrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans submit_url=url('devhub.personas.submit') %}
|
||||
{% trans submit_url=url('devhub.themes.submit') %}
|
||||
<a href="{{ submit_url }}">Finished Your Design? You can submit it right now!</a>
|
||||
{% endtrans %}
|
||||
</p>
|
||||
|
@ -251,7 +251,7 @@
|
|||
</ul>
|
||||
<p class="screenshot"><img src="{{ MEDIA_URL }}img/docs/themes/submission-step.jpg"></p>
|
||||
|
||||
<p class="call-to-submit"><a href="{{ url('devhub.personas.submit') }}" class="button prominent">{{ _('Submit Your Theme Now') }}</a></p>
|
||||
<p class="call-to-submit"><a href="{{ url('devhub.themes.submit') }}" class="button prominent">{{ _('Submit Your Theme Now') }}</a></p>
|
||||
</div>
|
||||
{{ sidebar() }}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
{% if theme %}
|
||||
{% set urls = [(addon.get_dev_url(), _('Edit Listing'))] %}
|
||||
{% else %}
|
||||
{% set urls = [
|
||||
(addon.get_dev_url(), _('Edit Listing')),
|
||||
(addon.get_dev_url('owner'), _('Manage Authors') if webapp
|
||||
else _('Manage Authors & License')),
|
||||
(addon.get_dev_url('profile'), _('Manage Developer Profile')),
|
||||
(addon.get_dev_url('payments'), _('Manage Payments')),
|
||||
(addon.get_dev_url('versions'), _('Manage App Status') if webapp
|
||||
else _('Manage Status & Versions')),
|
||||
] %}
|
||||
{% endif %}
|
||||
{% set urls = [
|
||||
(addon.get_dev_url(), _('Edit Listing')),
|
||||
(addon.get_dev_url('owner'), _('Manage Authors') if webapp
|
||||
else _('Manage Authors & License')),
|
||||
(addon.get_dev_url('profile'), _('Manage Developer Profile')),
|
||||
(addon.get_dev_url('payments'), _('Manage Payments')),
|
||||
(addon.get_dev_url('versions'), _('Manage App Status') if webapp
|
||||
else _('Manage Status & Versions')),
|
||||
] %}
|
||||
{% if addon.is_premium() and waffle.switch('allow-refund') %}
|
||||
{% do urls.insert(4, (addon.get_dev_url('refunds'), loc('Manage Refunds'))) %}
|
||||
{% endif %}
|
||||
|
|
|
@ -60,23 +60,17 @@
|
|||
</tr>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro select_cats(max, form, webapp=False, theme=False) %}
|
||||
{% macro select_cats(max, form, webapp=False) %}
|
||||
<div class="addon-app-cats">
|
||||
<label>
|
||||
{% if webapp %}
|
||||
{{ loc('Select <b>up to {0}</b> categories for this app:')|f(max)|safe }}
|
||||
{% else %}
|
||||
{% if theme %}
|
||||
{{ ngettext('Select <b>up to {0}</b> {1} category for this theme:',
|
||||
'Select <b>up to {0}</b> {1} categories for this theme:',
|
||||
max)|f(max, form.app.pretty if form.app else '')|safe }}
|
||||
{% else %}
|
||||
{# L10n: {0} is the maximum number of add-on categories allowed.
|
||||
{1} is the application name. #}
|
||||
{{ 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 if form.app else '')|safe }}
|
||||
{% endif %}
|
||||
{# L10n: {0} is the maximum number of add-on categories allowed.
|
||||
{1} is the application name. #}
|
||||
{{ 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 if form.app else '')|safe }}
|
||||
{% endif %}
|
||||
</label>
|
||||
{{ form.application }}
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
{% endtrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if not item.addon.is_incomplete() and not item.addon.is_disabled %}
|
||||
{% if not item.addon.is_persona() and not item.addon.is_incomplete() and not item.addon.is_disabled %}
|
||||
<p class="upload-new-version">
|
||||
<a href="{{ item.addon.get_dev_url('versions') }}#version-upload">
|
||||
{{ _('Upload New Version') }}</a>
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
{% extends "devhub/base_impala.html" %}
|
||||
|
||||
{% from "includes/forms.html" import pretty_field, tip %}
|
||||
{% from "devhub/includes/macros.html" import some_html_tip %}
|
||||
|
||||
{% set title = _('Edit Listing') %}
|
||||
|
||||
{% block title %}
|
||||
{{ dev_page_title(title, addon) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="primary full">
|
||||
{{ dev_breadcrumbs(addon) }}
|
||||
|
||||
<h1>{{ title }}</h1>
|
||||
|
||||
<div class="island swagger theme-info c">
|
||||
<div class="addon-name-and-icon c">
|
||||
<img class="addon-icon" src="{{ addon.icon_url }}">
|
||||
<strong class="addon-name">{{ addon.name }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
{{ persona_preview(addon.persona, linked=False) }}
|
||||
<div class="info">
|
||||
<p>
|
||||
<strong>{{ _('Status:') }}</strong>
|
||||
<span class="{{ status_class(addon) }}"><b>{{ amo.STATUS_CHOICES[addon.status] }}</b></span>
|
||||
</p>
|
||||
{% set position = get_position(addon) %}
|
||||
{% if position %}
|
||||
<p>
|
||||
<strong>{{ _('Queue Position:') }}</strong>
|
||||
{% trans position=position.pos|numberfmt,
|
||||
total=position.total|numberfmt %}
|
||||
{{ position }} of {{ total }}
|
||||
{% endtrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if addon.is_public() %}
|
||||
<p><a href="{{ addon.get_url_path() }}">{{ _('View Listing') }}</a></p>
|
||||
{% endif %}
|
||||
<p><a href="{{ url('devhub.feed', addon.slug) }}">
|
||||
{{ _('View Recent Changes') }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="island prettyform" id="submit-persona">
|
||||
<form method="post">
|
||||
{{ csrf() }}
|
||||
{{ form.unsaved_data }}
|
||||
<fieldset>
|
||||
<legend>{{ _('Theme Details') }}</legend>
|
||||
<ul>
|
||||
{{ pretty_field(form.name, label=_('Give your Theme a name.'), validate=True) }}
|
||||
<li class="row">
|
||||
{% set before_slug = ('<span class="url-prefix">%s/addon/</span>' % settings.SITE_URL)|safe %}
|
||||
{{ pretty_field(form.slug, label=_('Supply a pretty URL for your detail page.'), tag=None, before=before_slug, validate=True) }}
|
||||
<span class="note">
|
||||
{{ _('Please use only letters, numbers, underscores, and dashes in your URL.') }}
|
||||
</span>
|
||||
</li>
|
||||
{{ pretty_field(form.category, label=_('Select the category that best describes your Theme.'),
|
||||
class='row radios addon-cats', validate=True) }}
|
||||
<li class="row">
|
||||
{{ pretty_field(form.tags, label=_('Add some tags to describe your Theme.'), tag=None, opt=True, validate=True) }}
|
||||
<span class="note">
|
||||
{{ ngettext('Comma-separated, minimum of {0} character.',
|
||||
'Comma-separated, minimum of {0} characters.',
|
||||
amo.MIN_TAG_LENGTH)|f(amo.MIN_TAG_LENGTH) }}
|
||||
{{ _('Example: dark, cinema, noir. Limit 20.') }}
|
||||
</span>
|
||||
</li>
|
||||
<li class="row c">
|
||||
{{ pretty_field(form.summary, label=_('Describe your Theme.'),
|
||||
tooltip=_("A short explanation of your theme's
|
||||
basic functionality that is displayed in
|
||||
search and browse listings, as well as at
|
||||
the top of your theme's details page."),
|
||||
tag=None, opt=True, validate=True) }}
|
||||
<div class="note">
|
||||
{{ some_html_tip() }}
|
||||
<span class="char-count" data-for="{{ form.summary.auto_id }}"
|
||||
data-maxlength="{{ form.summary.field.max_length }}"></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{{ _('Theme License') }}</legend>
|
||||
<div id="cc-chooser">
|
||||
{{ form.license }}
|
||||
{{ form.license.errors }}
|
||||
<h3>{{ _("Can others share your Theme, as long as you're given credit?") }}</h3>
|
||||
<ul class="radios">
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" name="cc-attrib" value="0" data-cc="cc-attrib">
|
||||
{{ _('Yes') }}
|
||||
{{ tip(None, _('The licensor permits others to copy, distribute, '
|
||||
'display, and perform the work, including for '
|
||||
'commercial purposes.')) }}
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" name="cc-attrib" value="1" data-cc="copyr">
|
||||
{{ _('No') }}
|
||||
{{ tip(None, _('The licensor permits others to copy, distribute, '
|
||||
'display, and perform the work for non-commercial '
|
||||
'purposes only.')) }}
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<h3 class="noncc">{{ _('Can others make commercial use of your Theme?') }}</h3>
|
||||
<ul class="noncc radios">
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" name="cc-noncom" value="0">
|
||||
{{ _('Yes') }}
|
||||
{{ tip(None, _('The licensor permits others to copy, distribute, '
|
||||
'display, and perform the work, including for '
|
||||
'commercial purposes.')) }}
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" name="cc-noncom" value="1" data-cc="cc-noncom">
|
||||
{{ _('No') }}
|
||||
{{ tip(None, _('The licensor permits others to copy, distribute, '
|
||||
'display, and perform the work for non-commercial '
|
||||
'purposes only.')) }}
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<h3 class="noncc">{{ _('Can others create derivative works from your Theme?') }}</h3>
|
||||
<ul class="noncc radios">
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" name="cc-noderiv" value="0">
|
||||
{{ _('Yes') }}
|
||||
{{ tip(None, _('The licensor permits others to copy, distribute, '
|
||||
'display and perform the work, as well as make '
|
||||
'derivative works based on it.')) }}
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" name="cc-noderiv" value="1" data-cc="cc-share">
|
||||
{{ _('Yes, as long as they share alike') }}
|
||||
{{ tip(None, _('The licensor permits others to distribute derivative'
|
||||
'works only under the same license or one compatible '
|
||||
"with the one that governs the licensor's work.")) }}
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" name="cc-noderiv" value="2" data-cc="cc-noderiv">
|
||||
{{ _('No') }}
|
||||
{{ tip(None, _('The licensor permits others to copy, distribute and '
|
||||
'transmit only unaltered copies of the work — not '
|
||||
'derivative works based on it.')) }}
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<div id="persona-license">
|
||||
<p>{{ _('Your Theme will be released under the following license:') }}</p>
|
||||
<p id="cc-license" class="license icon"></p>
|
||||
<p class="select-license">
|
||||
<a href="#">{{ _('Select a different license.') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="persona-license-list">
|
||||
<h3>{{ _('Select a license for your Theme.') }}</h3>
|
||||
<ul class="radios">
|
||||
{% for license in amo.PERSONA_LICENSES %}
|
||||
<li><label><input type="radio" name="license" value="{{ license.id }}">
|
||||
{{ license.name }}</label></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset id="persona-design">
|
||||
<legend>{{ _('Theme Design') }}</legend>
|
||||
<ul>
|
||||
<li id="persona-header" class="row">
|
||||
<h3>{{ _('Header') }}</h3>
|
||||
<img class="preview loaded" src="{{ addon.persona.header_url }}">
|
||||
</li>
|
||||
<li id="persona-footer" class="row">
|
||||
<h3>{{ _('Footer') }}</h3>
|
||||
<img class="preview loaded" src="{{ addon.persona.footer_url }}">
|
||||
</li>
|
||||
</ul>
|
||||
<h3>{{ _('Select colors for your Theme.') }}</h3>
|
||||
<ul class="colors">
|
||||
{{ pretty_field(form.textcolor, label=_('Foreground Text'),
|
||||
tooltip=_('This is the color of the tab text.'),
|
||||
validate=True) }}
|
||||
{{ pretty_field(form.accentcolor, label=_('Background'),
|
||||
tooltip=_('This is the color of the tabs.'),
|
||||
validate=True) }}
|
||||
</ul>
|
||||
</fieldset>
|
||||
<fieldset id="persona-preview">
|
||||
<legend>{{ _('Preview') }}</legend>
|
||||
<div class="persona persona-large persona-preview">
|
||||
<div class="persona-viewer" data-browsertheme="{{ addon.persona.json_data }}"
|
||||
style="background-image:url({{ addon.persona.header_url }})">
|
||||
<div class="details">
|
||||
<span class="title" id="persona-preview-name">
|
||||
{{ _("Your Theme's Name") }}</span>
|
||||
<span class="author">
|
||||
{% set amo_user = request.amo_user %}
|
||||
{% trans user=amo_user.username,
|
||||
profile_url=url('users.profile', amo_user.id) %}
|
||||
by <a href="{{ profile_url }}" target="_blank">{{ user }}</a>
|
||||
{% endtrans %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="listing-footer">
|
||||
<button class="button prominent" disabled type="submit">{{ _('Save Changes') }}</button>
|
||||
{% if check_addon_ownership(request, addon) and addon.can_be_deleted() %}
|
||||
<a href="#" class="button prominent delete-addon">{{ _('Delete Theme') }}</a>
|
||||
<div class="modal-delete modal hidden">
|
||||
{% include 'devhub/addons/listing/delete_form.html' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -66,14 +66,14 @@
|
|||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{{ _('Theme License') }}</legend>
|
||||
<div id="cc-chooser"{% if form.license.value() %} class="hidden"{% endif %}>
|
||||
<div id="cc-chooser">
|
||||
{{ form.license }}
|
||||
{{ form.license.errors }}
|
||||
<h3>{{ _("Can others share your Theme, as long as you're given credit?") }}</h3>
|
||||
<ul class="radios">
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" name="cc-attrib" value="0">
|
||||
<input type="radio" name="cc-attrib" value="0" data-cc="cc-attrib">
|
||||
{{ _('Yes') }}
|
||||
{{ tip(None, _('The licensor permits others to copy, distribute, '
|
||||
'display, and perform the work, including for '
|
||||
|
@ -82,7 +82,7 @@
|
|||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" name="cc-attrib" value="1">
|
||||
<input type="radio" name="cc-attrib" value="1" data-cc="copyr">
|
||||
{{ _('No') }}
|
||||
{{ tip(None, _('The licensor permits others to copy, distribute, '
|
||||
'display, and perform the work for non-commercial '
|
||||
|
@ -103,7 +103,7 @@
|
|||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" name="cc-noncom" value="1">
|
||||
<input type="radio" name="cc-noncom" value="1" data-cc="cc-noncom">
|
||||
{{ _('No') }}
|
||||
{{ tip(None, _('The licensor permits others to copy, distribute, '
|
||||
'display, and perform the work for non-commercial '
|
||||
|
@ -124,7 +124,7 @@
|
|||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" name="cc-noderiv" value="1">
|
||||
<input type="radio" name="cc-noderiv" value="1" data-cc="cc-share">
|
||||
{{ _('Yes, as long as they share alike') }}
|
||||
{{ tip(None, _('The licensor permits others to distribute derivative'
|
||||
'works only under the same license or one compatible '
|
||||
|
@ -133,7 +133,7 @@
|
|||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" name="cc-noderiv" value="2">
|
||||
<input type="radio" name="cc-noderiv" value="2" data-cc="cc-noderiv">
|
||||
{{ _('No') }}
|
||||
{{ tip(None, _('The licensor permits others to copy, distribute and '
|
||||
'transmit only unaltered copies of the work — not '
|
||||
|
@ -149,7 +149,7 @@
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="persona-license-list"{% if not form.license.value() %} class="hidden"{% endif %}>
|
||||
<div id="persona-license-list">
|
||||
<h3>{{ _('Select a license for your Theme.') }}</h3>
|
||||
<ul class="radios">
|
||||
{% for license in amo.PERSONA_LICENSES %}
|
||||
|
|
|
@ -15,12 +15,13 @@ from amo.tests.test_helpers import get_image_path
|
|||
from amo.urlresolvers import reverse
|
||||
import paypal
|
||||
from applications.models import AppVersion
|
||||
from addons.forms import NewPersonaForm
|
||||
from addons.models import Addon, Category, Charity
|
||||
from addons.forms import EditThemeForm, ThemeForm
|
||||
from addons.models import Addon, Category, Charity, Persona
|
||||
from devhub import forms
|
||||
from files.helpers import copyfileobj
|
||||
from files.models import FileUpload
|
||||
from market.models import AddonPremium
|
||||
from tags.models import Tag
|
||||
from users.models import UserProfile
|
||||
from versions.models import ApplicationsVersions, License
|
||||
|
||||
|
@ -181,7 +182,7 @@ class TestPremiumForm(amo.tests.TestCase):
|
|||
eq_(['price'], form.errors.keys())
|
||||
|
||||
|
||||
class TestNewPersonaForm(amo.tests.TestCase):
|
||||
class TestThemeForm(amo.tests.TestCase):
|
||||
fixtures = ['base/apps', 'base/user_2519']
|
||||
|
||||
def setUp(self):
|
||||
|
@ -214,7 +215,7 @@ class TestNewPersonaForm(amo.tests.TestCase):
|
|||
return data
|
||||
|
||||
def post(self, **kw):
|
||||
self.form = NewPersonaForm(self.get_dict(**kw), request=self.request)
|
||||
self.form = ThemeForm(self.get_dict(**kw), request=self.request)
|
||||
return self.form
|
||||
|
||||
def test_name_unique(self):
|
||||
|
@ -394,3 +395,91 @@ class TestNewPersonaForm(amo.tests.TestCase):
|
|||
full_dst=[os.path.join(dst, 'preview.png'),
|
||||
os.path.join(dst, 'icon.png')],
|
||||
set_modified_on=[addon])
|
||||
|
||||
|
||||
class TestEditThemeForm(amo.tests.TestCase):
|
||||
fixtures = ['base/apps', 'base/user_2519']
|
||||
|
||||
def setUp(self):
|
||||
self.populate()
|
||||
self.request = mock.Mock()
|
||||
self.request.groups = ()
|
||||
self.request.amo_user = mock.Mock()
|
||||
self.request.amo_user.username = 'swagyismymiddlename'
|
||||
self.request.amo_user.is_authenticated.return_value = True
|
||||
|
||||
def populate(self):
|
||||
self.instance = Addon.objects.create(type=amo.ADDON_PERSONA,
|
||||
status=amo.STATUS_PUBLIC, slug='swag-overload',
|
||||
name='Bands Make Me Dance', summary='tha summary')
|
||||
self.cat = Category.objects.create(
|
||||
type=amo.ADDON_PERSONA, name='xxxx')
|
||||
self.instance.addoncategory_set.create(category=self.cat)
|
||||
self.license = License.objects.create(id=amo.LICENSE_CC_BY.id)
|
||||
Persona.objects.create(persona_id=0, addon_id=self.instance.id,
|
||||
license_id=self.license.id, accentcolor='C0FFEE',
|
||||
textcolor='EFFFFF')
|
||||
Tag(tag_text='sw').save_tag(self.instance)
|
||||
Tag(tag_text='ag').save_tag(self.instance)
|
||||
|
||||
def get_dict(self, **kw):
|
||||
data = {
|
||||
'accentcolor': '#C0FFEE',
|
||||
'category': self.cat.id,
|
||||
'license': self.license.id,
|
||||
'name': self.instance.name.id,
|
||||
'slug': self.instance.slug,
|
||||
'summary': self.instance.summary.id,
|
||||
'tags': 'ag, sw',
|
||||
'textcolor': '#EFFFFF'
|
||||
}
|
||||
data.update(**kw)
|
||||
return data
|
||||
|
||||
def test_initial(self):
|
||||
self.form = EditThemeForm(None, request=self.request,
|
||||
instance=self.instance)
|
||||
eq_(self.form.initial, self.get_dict())
|
||||
|
||||
def test_success(self):
|
||||
other_cat = Category.objects.create(type=amo.ADDON_PERSONA)
|
||||
other_license = License.objects.create(id=amo.LICENSE_CC_BY_NC_SA.id)
|
||||
data = {
|
||||
'accentcolor': '#EFF0FF',
|
||||
'category': other_cat.id,
|
||||
'license': other_license.id,
|
||||
'name': 'All Day I Dream About Swag',
|
||||
'slug': 'swag-lifestyle',
|
||||
'summary': 'ADIDAS',
|
||||
'tags': 'ag',
|
||||
'textcolor': '#CACACA'
|
||||
}
|
||||
self.form = EditThemeForm(data, request=self.request,
|
||||
instance=self.instance)
|
||||
eq_(self.form.initial, self.get_dict())
|
||||
eq_(self.form.data, data)
|
||||
eq_(self.form.is_valid(), True, self.form.errors)
|
||||
self.form.save()
|
||||
|
||||
self.instance = self.instance.reload()
|
||||
eq_(unicode(self.instance.persona.accentcolor),
|
||||
data['accentcolor'].lstrip('#'))
|
||||
eq_(self.instance.categories.all()[0].id, data['category'])
|
||||
eq_(self.instance.persona.license.id, data['license'])
|
||||
eq_(unicode(self.instance.name), data['name'])
|
||||
eq_(unicode(self.instance.summary), data['summary'])
|
||||
self.assertSetEqual(
|
||||
self.instance.tags.values_list('tag_text', flat=True),
|
||||
[data['tags']])
|
||||
eq_(unicode(self.instance.persona.textcolor),
|
||||
data['textcolor'].lstrip('#'))
|
||||
|
||||
def test_name_unique(self):
|
||||
data = self.get_dict(name='Bands Make You Dance')
|
||||
Addon.objects.create(type=amo.ADDON_PERSONA,
|
||||
status=amo.STATUS_PUBLIC, name=data['name'])
|
||||
self.form = EditThemeForm(data, request=self.request,
|
||||
instance=self.instance)
|
||||
eq_(self.form.is_valid(), False)
|
||||
eq_(self.form.errors,
|
||||
{'name': ['This name is already in use. Please choose another.']})
|
||||
|
|
|
@ -18,7 +18,7 @@ class TestSubmitPersona(amo.tests.TestCase):
|
|||
def setUp(self):
|
||||
assert self.client.login(username='regular@mozilla.com',
|
||||
password='password')
|
||||
self.url = reverse('devhub.personas.submit')
|
||||
self.url = reverse('devhub.themes.submit')
|
||||
|
||||
def get_img_urls(self):
|
||||
return (
|
||||
|
|
|
@ -43,14 +43,12 @@ def marketplace_patterns(prefix):
|
|||
name='devhub.%s.market.4' % prefix),
|
||||
)
|
||||
|
||||
# These will all start with /theme/<theme_slug>/
|
||||
# These will all start with /theme/<slug>/
|
||||
theme_detail_patterns = patterns('',
|
||||
url('^$', lambda r,
|
||||
addon_id: redirect('devhub.themes.edit', addon_id,
|
||||
permanent=True)),
|
||||
url('^edit$', views.edit, name='devhub.themes.edit'),
|
||||
url('^edit_(?P<section>[^/]+)(?:/(?P<editable>[^/]+))?$',
|
||||
views.addons_section, name='devhub.themes.section'),
|
||||
addon_id: redirect('devhub.themes.edit', addon_id, permanent=True)),
|
||||
url('^edit$', views.edit_theme, name='devhub.themes.edit'),
|
||||
url('^delete$', views.delete, name='devhub.themes.delete'),
|
||||
)
|
||||
|
||||
# These will all start with /app/<app_slug>/
|
||||
|
@ -247,10 +245,10 @@ urlpatterns = decorate(write, patterns('',
|
|||
url('^ajax/addon/%s/' % ADDON_ID, include(ajax_patterns)),
|
||||
|
||||
# Themes submission.
|
||||
url('^theme/submit/?$', views.submit_persona,
|
||||
name='devhub.personas.submit'),
|
||||
url('^theme/%s/submit/done$' % ADDON_ID, views.submit_persona_done,
|
||||
name='devhub.personas.submit.done'),
|
||||
url('^theme/submit/?$', views.submit_theme,
|
||||
name='devhub.themes.submit'),
|
||||
url('^theme/%s/submit/done$' % ADDON_ID, views.submit_theme_done,
|
||||
name='devhub.themes.submit.done'),
|
||||
url('^theme/submit/upload/'
|
||||
'(?P<upload_type>persona_header|persona_footer)$',
|
||||
views.ajax_upload_image, name='devhub.personas.upload_persona'),
|
||||
|
|
|
@ -25,7 +25,7 @@ from PIL import Image
|
|||
from session_csrf import anonymous_csrf
|
||||
from tower import ugettext_lazy as _lazy, ugettext as _
|
||||
import waffle
|
||||
from waffle.decorators import waffle_switch
|
||||
from waffle.decorators import waffle_flag, waffle_switch
|
||||
|
||||
from applications.models import Application, AppVersion
|
||||
import amo
|
||||
|
@ -295,19 +295,14 @@ def feed(request, addon_id=None):
|
|||
return jingo.render(request, 'devhub/addons/activity.html', data)
|
||||
|
||||
|
||||
@dev_required(webapp=True, theme=True)
|
||||
def edit(request, addon_id, addon, webapp=False, theme=False):
|
||||
url_prefix = 'addons'
|
||||
if webapp:
|
||||
url_prefix = 'apps'
|
||||
elif theme:
|
||||
url_prefix = 'themes'
|
||||
@dev_required(webapp=True)
|
||||
def edit(request, addon_id, addon, webapp=False):
|
||||
url_prefix = 'apps' if webapp else 'addons'
|
||||
|
||||
data = {
|
||||
'page': 'edit',
|
||||
'addon': addon,
|
||||
'webapp': webapp,
|
||||
'theme': theme,
|
||||
'url_prefix': url_prefix,
|
||||
'valid_slug': addon.slug,
|
||||
'tags': addon.tags.not_blacklisted().values_list('tag_text', flat=True),
|
||||
|
@ -321,6 +316,20 @@ def edit(request, addon_id, addon, webapp=False, theme=False):
|
|||
return jingo.render(request, 'devhub/addons/edit.html', data)
|
||||
|
||||
|
||||
@dev_required(theme=True)
|
||||
@waffle_flag('submit-personas')
|
||||
def edit_theme(request, addon_id, addon, theme=False):
|
||||
form = addon_forms.EditThemeForm(data=request.POST or None,
|
||||
request=request, instance=addon)
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
form.save()
|
||||
return redirect('devhub.themes.edit', addon.reload().slug)
|
||||
return jingo.render(request, 'devhub/personas/edit.html', {
|
||||
'addon': addon,
|
||||
'form': form
|
||||
})
|
||||
|
||||
|
||||
@dev_required(owner_for_post=True, webapp=True)
|
||||
def delete(request, addon_id, addon, webapp=False):
|
||||
# Database deletes only allowed for free or incomplete addons.
|
||||
|
@ -1096,12 +1105,11 @@ def ajax_dependencies(request, addon_id, addon):
|
|||
return s(request, excluded_ids=[addon_id]).items
|
||||
|
||||
|
||||
@dev_required(webapp=True, theme=True)
|
||||
@dev_required(webapp=True)
|
||||
def addons_section(request, addon_id, addon, section, editable=False,
|
||||
webapp=False, theme=False):
|
||||
webapp=False):
|
||||
basic = addon_forms.AppFormBasic if webapp else addon_forms.AddonFormBasic
|
||||
models = {'basic': basic,
|
||||
'license': addon_forms.ThemeLicenseForm,
|
||||
'media': addon_forms.AddonFormMedia,
|
||||
'details': addon_forms.AddonFormDetails,
|
||||
'support': addon_forms.AddonFormSupport,
|
||||
|
@ -1173,15 +1181,10 @@ def addons_section(request, addon_id, addon, section, editable=False,
|
|||
else:
|
||||
form = False
|
||||
|
||||
url_prefix = 'addons'
|
||||
if webapp:
|
||||
url_prefix = 'apps'
|
||||
elif theme:
|
||||
url_prefix = 'themes'
|
||||
url_prefix = 'apps' if webapp else 'addons'
|
||||
|
||||
data = {'addon': addon,
|
||||
'webapp': webapp,
|
||||
'theme': theme,
|
||||
'url_prefix': url_prefix,
|
||||
'form': form,
|
||||
'editable': editable,
|
||||
|
@ -1794,23 +1797,21 @@ def submit_bump(request, addon_id, addon, webapp=False):
|
|||
|
||||
|
||||
@login_required
|
||||
def submit_persona(request):
|
||||
if not waffle.flag_is_active(request, 'submit-personas'):
|
||||
raise PermissionDenied
|
||||
form = addon_forms.NewPersonaForm(data=request.POST or None,
|
||||
files=request.FILES or None,
|
||||
request=request)
|
||||
@waffle_flag('submit-personas')
|
||||
def submit_theme(request):
|
||||
form = addon_forms.ThemeForm(data=request.POST or None,
|
||||
files=request.FILES or None,
|
||||
request=request)
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
addon = form.save()
|
||||
return redirect('devhub.personas.submit.done', addon.slug)
|
||||
return redirect('devhub.themes.submit.done', addon.slug)
|
||||
return jingo.render(request, 'devhub/personas/submit.html',
|
||||
dict(form=form))
|
||||
|
||||
|
||||
@dev_required(theme=True)
|
||||
def submit_persona_done(request, addon_id, addon, theme):
|
||||
if not waffle.flag_is_active(request, 'submit-personas'):
|
||||
raise PermissionDenied
|
||||
@waffle_flag('submit-personas')
|
||||
def submit_theme_done(request, addon_id, addon, theme):
|
||||
if addon.is_public():
|
||||
return redirect(addon.get_url_path())
|
||||
return jingo.render(request, 'devhub/personas/submit_done.html',
|
||||
|
|
|
@ -11,6 +11,7 @@ from tower import ugettext as _, ugettext_lazy as _lazy, ungettext as ngettext
|
|||
|
||||
import amo
|
||||
from addons.helpers import new_context
|
||||
from addons.models import Addon
|
||||
from amo.helpers import absolutify, breadcrumbs, page_title
|
||||
from amo.urlresolvers import reverse
|
||||
from amo.utils import send_mail as amo_send_mail
|
||||
|
@ -319,22 +320,36 @@ def send_mail(template, subject, emails, context, perm_setting=None):
|
|||
use_blacklist=False, perm_setting=perm_setting)
|
||||
|
||||
|
||||
@register.function
|
||||
def get_position(addon):
|
||||
version = addon.latest_version
|
||||
if addon.is_persona() and addon.is_pending():
|
||||
pending_themes = (Addon.objects
|
||||
.filter(status=amo.STATUS_PENDING,
|
||||
type=amo.ADDON_PERSONA)
|
||||
.order_by('created').values_list('id', flat=True))
|
||||
for idx, pending in enumerate(pending_themes, start=1):
|
||||
if pending == addon.id:
|
||||
position = idx
|
||||
break
|
||||
total = pending_themes.count()
|
||||
return {'mins': 1 * total, 'pos': position, 'total': total}
|
||||
|
||||
if not version:
|
||||
return False
|
||||
else:
|
||||
version = addon.latest_version
|
||||
|
||||
q = version.current_queue
|
||||
if not q:
|
||||
return False
|
||||
if not version:
|
||||
return False
|
||||
|
||||
mins_query = q.objects.filter(id=addon.id)
|
||||
if mins_query.count() > 0:
|
||||
mins = mins_query[0].waiting_time_min
|
||||
pos = q.objects.having('waiting_time_min >=', mins).count()
|
||||
total = q.objects.count()
|
||||
return dict(mins=mins, pos=pos, total=total)
|
||||
q = version.current_queue
|
||||
if not q:
|
||||
return False
|
||||
|
||||
mins_query = q.objects.filter(id=addon.id)
|
||||
if mins_query.count() > 0:
|
||||
mins = mins_query[0].waiting_time_min
|
||||
pos = q.objects.having('waiting_time_min >=', mins).count()
|
||||
total = q.objects.count()
|
||||
return dict(mins=mins, pos=pos, total=total)
|
||||
|
||||
return False
|
||||
|
||||
|
|
|
@ -19,9 +19,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
#submit-addon, .submit-theme {
|
||||
margin-bottom: 1.5em;
|
||||
p.submit-cta {
|
||||
margin-bottom: 1em;
|
||||
padding: 0;
|
||||
+ p.submit-cta {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
.button {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
|
|
|
@ -77,10 +77,8 @@ ul.license {
|
|||
.persona-large {
|
||||
max-width: 680px;
|
||||
p {
|
||||
background-image: url(../../img/zamboni/mobile/loading-white.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
.background-size(auto 32px);
|
||||
background: url(../../img/zamboni/mobile/loading-white.png) center no-repeat;
|
||||
background-size: auto 32px;
|
||||
color: #fff;
|
||||
display: none;
|
||||
font: 18px Georgia, serif;
|
||||
|
@ -119,7 +117,7 @@ ul.license {
|
|||
display: table-cell;
|
||||
}
|
||||
|
||||
#submit-persona, #addon-edit-license {
|
||||
#submit-persona {
|
||||
.note {
|
||||
display: block;
|
||||
padding-top: 2px;
|
||||
|
@ -127,31 +125,27 @@ ul.license {
|
|||
.addon-cats label + ul {
|
||||
.columns(4);
|
||||
}
|
||||
#persona-license {
|
||||
display: none;
|
||||
}
|
||||
#persona-license-list {
|
||||
ul {
|
||||
margin: .5em 0 0;
|
||||
}
|
||||
}
|
||||
#persona-license,
|
||||
#persona-license-list {
|
||||
background-color: darken(#FCFDFE, 10%);
|
||||
.border-radius(5px);
|
||||
border-radius: 5px;
|
||||
display: none;
|
||||
padding: 1em;
|
||||
width: 400px;
|
||||
p {
|
||||
margin: 0;
|
||||
&.license {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
height: auto;
|
||||
margin: .5em 0;
|
||||
padding-left: 20px;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
p.license {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
height: auto;
|
||||
margin: .5em 0;
|
||||
padding-left: 20px;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
#persona-license-list {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.colors {
|
||||
li.row {
|
||||
|
@ -172,7 +166,6 @@ ul.license {
|
|||
}
|
||||
.preview {
|
||||
background-color: #ccc;
|
||||
display: none;
|
||||
width: 100%;
|
||||
}
|
||||
#persona-header .preview.loading {
|
||||
|
@ -186,13 +179,13 @@ ul.license {
|
|||
display: block;
|
||||
}
|
||||
.loading:before {
|
||||
background: #444 url(../../img/zamboni/mobile/loading-white.png) no-repeat 50% 50%;
|
||||
.background-size(auto 70%);
|
||||
background: #444 url(../../img/zamboni/mobile/loading-white.png) center no-repeat;
|
||||
background-size: auto 70%;
|
||||
content: "\00a0";
|
||||
display: block;
|
||||
height: 100%;
|
||||
-moz-transition: opacity .5s;
|
||||
opacity: .4;
|
||||
transition: opacity .5s;
|
||||
width: 100%;
|
||||
}
|
||||
.loaded:before {
|
||||
|
@ -203,7 +196,7 @@ ul.license {
|
|||
}
|
||||
}
|
||||
#persona-preview .persona-viewer {
|
||||
.background-size(cover);
|
||||
background-size: cover;
|
||||
.gradient-two-color(#ccc, #bbb);
|
||||
}
|
||||
}
|
||||
|
@ -213,6 +206,14 @@ ul.license {
|
|||
}
|
||||
}
|
||||
|
||||
#persona-header img {
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
#persona-footer img {
|
||||
max-height: 100px;
|
||||
}
|
||||
|
||||
.done-next-steps li {
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
@ -235,10 +236,7 @@ ul.license {
|
|||
top: 2px;
|
||||
}
|
||||
}
|
||||
#persona-license-list {
|
||||
display: none;
|
||||
}
|
||||
#persona-license, #persona-license-list {
|
||||
#persona-license {
|
||||
margin-bottom: 13px;
|
||||
}
|
||||
}
|
||||
|
@ -261,3 +259,57 @@ ul.license {
|
|||
margin: 0 -1.1em -1.1em -1.1em;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-info {
|
||||
float: left;
|
||||
.addon-icon {
|
||||
border: 1px solid #ccc;
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
.addon-name-and-icon,
|
||||
.addon-name-and-icon + div {
|
||||
float: left;
|
||||
}
|
||||
.addon-name {
|
||||
font: 24px/36px @sans-stack;
|
||||
padding-left: 8px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.persona {
|
||||
float: left;
|
||||
margin-top: 1em;
|
||||
}
|
||||
.info {
|
||||
float: left;
|
||||
margin-left: 1.5em;
|
||||
}
|
||||
[data-browsertheme] {
|
||||
background: #fff;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 6px;
|
||||
height: 100px;
|
||||
width: 680px;
|
||||
}
|
||||
}
|
||||
|
||||
.listing-footer {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
.button {
|
||||
float: left;
|
||||
margin: 0;
|
||||
}
|
||||
.delete-addon {
|
||||
float: right;
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
padding: 12px 16px;
|
||||
&:not([disabled]) {
|
||||
background: #e33;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,11 @@ a {
|
|||
float: left;
|
||||
margin-bottom: 15px;
|
||||
padding: 14px 14px 16px;
|
||||
&.swagger {
|
||||
.box-shadow(0 -2px 0 rgba(223, 223, 223, .3) inset, 0 0 1px rgba(0, 0, 0, 0.1));
|
||||
.gradient-two-color(#FCFCFC, #F4F4F4);
|
||||
border-color: @light-gray;
|
||||
}
|
||||
> section {
|
||||
border-bottom: 1px solid #C9DDF2;
|
||||
border-top: 1px solid transparent;
|
||||
|
|
|
@ -1,123 +1,14 @@
|
|||
z.licenses = {
|
||||
'copyr': {
|
||||
'id': 7,
|
||||
'name': gettext('All Rights Reserved')
|
||||
},
|
||||
'cc-attrib': {
|
||||
'id': 9,
|
||||
'name': gettext('Creative Commons Attribution 3.0'),
|
||||
'url': 'http://creativecommons.org/licenses/by/3.0/'
|
||||
},
|
||||
'cc-attrib cc-noncom': {
|
||||
'id': 10,
|
||||
'name': gettext('Creative Commons Attribution-NonCommercial 3.0'),
|
||||
'url': 'http://creativecommons.org/licenses/by-nc/3.0/'
|
||||
},
|
||||
'cc-attrib cc-noncom cc-noderiv': {
|
||||
'id': 11,
|
||||
'name': gettext('Creative Commons Attribution-NonCommercial-NoDerivs 3.0'),
|
||||
'url': 'http://creativecommons.org/licenses/by/3.0/'
|
||||
},
|
||||
'cc-attrib cc-noncom cc-share': {
|
||||
'id': 8,
|
||||
'name': gettext('Creative Commons Attribution-NonCommercial-Share Alike 3.0'),
|
||||
'url': 'http://creativecommons.org/licenses/by-nc-sa/3.0/'
|
||||
},
|
||||
'cc-attrib cc-noderiv': {
|
||||
'id': 12,
|
||||
'name': gettext('Creative Commons Attribution-NoDerivs 3.0'),
|
||||
'url': 'http://creativecommons.org/licenses/by-nd/3.0/'
|
||||
},
|
||||
'cc-attrib cc-share': {
|
||||
'id': 13,
|
||||
'name': gettext('Creative Commons Attribution-ShareAlike 3.0'),
|
||||
'url': 'http://creativecommons.org/licenses/by/3.0/'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$(function() {
|
||||
(function() {
|
||||
if (!$('#submit-persona, #addon-edit-license').length) {
|
||||
return;
|
||||
}
|
||||
|
||||
function checkValid(form) {
|
||||
if (form) {
|
||||
$(form).find('button[type=submit]').attr('disabled', !form.checkValidity());
|
||||
$(form).find('button[type=submit]:not(.delete)').attr('disabled', !form.checkValidity());
|
||||
}
|
||||
}
|
||||
$(document).delegate('input, select, textarea', 'change keyup paste', function(e) {
|
||||
checkValid(e.target.form);
|
||||
});
|
||||
$('form').each(function() {
|
||||
checkValid(this);
|
||||
});
|
||||
|
||||
initLicense();
|
||||
if (!$('#addon-edit-license').length) {
|
||||
initCharCount();
|
||||
initPreview();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function initLicense() {
|
||||
var ccClasses = {
|
||||
'cc-attrib': ['cc-attrib', 'copyr'],
|
||||
'cc-noncom': ['', 'cc-noncom'],
|
||||
'cc-noderiv': ['', 'cc-share', 'cc-noderiv']
|
||||
};
|
||||
function licenseUpdate() {
|
||||
var license = '';
|
||||
$.each(ccClasses, function(k, val) {
|
||||
v = val[+$('input[name=' + k + ']:checked').val()];
|
||||
if (v) {
|
||||
var is_copyr = (v == 'copyr');
|
||||
if (k == 'cc-attrib') {
|
||||
var $noncc = $('.noncc');
|
||||
// Hide the other radio buttons when copyright is selected.
|
||||
$noncc.toggleClass('disabled', is_copyr);
|
||||
if ($noncc.find('input[type=radio]:not(:checked)').length == 5) {
|
||||
$('input[name="cc-noncom"][value=1]').prop('checked', true);
|
||||
$('input[name="cc-noderiv"][value=2]').prop('checked', true);
|
||||
}
|
||||
}
|
||||
if (license != ' copyr') {
|
||||
license += ' ' + v;
|
||||
}
|
||||
}
|
||||
});
|
||||
license = $.trim(license);
|
||||
var l = z.licenses[license];
|
||||
if (!l) {
|
||||
return;
|
||||
}
|
||||
var license_txt = l['name'];
|
||||
if (l['url']) {
|
||||
license_txt = format('<a href="{0}" target="_blank">{1}</a>',
|
||||
l['url'], license_txt);
|
||||
}
|
||||
var $p = $('#persona-license');
|
||||
$p.show();
|
||||
$p.find('#cc-license').html(license_txt).attr('class', 'license icon ' + license);
|
||||
$('#id_license').val(l['id']);
|
||||
}
|
||||
$('input[name^="cc-"]').change(licenseUpdate);
|
||||
licenseUpdate();
|
||||
|
||||
function saveLicense() {
|
||||
$('#persona-license-list input[value="' + $('#id_license').val() + '"]').attr('checked', true);
|
||||
}
|
||||
$('#persona-license .select-license').click(_pd(function() {
|
||||
$('#persona-license-list').show();
|
||||
$('#cc-chooser').hide();
|
||||
saveLicense();
|
||||
}));
|
||||
saveLicense();
|
||||
}
|
||||
|
||||
|
||||
function initPreview() {
|
||||
function hex2rgb(hex) {
|
||||
var hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
|
||||
return {
|
||||
|
@ -127,128 +18,247 @@ function initPreview() {
|
|||
};
|
||||
}
|
||||
|
||||
var POST = {};
|
||||
function loadUnsaved() {
|
||||
return JSON.parse($('input[name="unsaved_data"]').val() || '{}');
|
||||
}
|
||||
|
||||
var $d = $('#persona-design'),
|
||||
upload_finished = function(e) {
|
||||
$(this).closest('.row').find('.preview').removeClass('loading');
|
||||
$('#submit-persona button').attr('disabled', false);
|
||||
updatePersona();
|
||||
},
|
||||
upload_start = function(e, file) {
|
||||
var $p = $(this).closest('.row'),
|
||||
$errors = $p.find('.errorlist');
|
||||
if ($errors.length == 2) {
|
||||
$errors.eq(0).remove();
|
||||
}
|
||||
$p.find('.errorlist').html('');
|
||||
$p.find('.preview').addClass('loading').removeClass('error-loading');
|
||||
$('#submit-persona button').attr('disabled', true);
|
||||
},
|
||||
upload_success = function(e, file, upload_hash) {
|
||||
var $p = $(this).closest('.row');
|
||||
$p.find('input[type="hidden"]').val(upload_hash);
|
||||
$p.find('input[type=file], .note').hide();
|
||||
$p.find('.preview').attr('src', file.dataURL).addClass('loaded');
|
||||
POST[upload_hash] = file.dataURL; // Remember this as "posted" data.
|
||||
updatePersona();
|
||||
$p.find('.preview, .reset').show();
|
||||
},
|
||||
upload_errors = function(e, file, errors) {
|
||||
var $p = $(this).closest('.row'),
|
||||
$errors = $p.find('.errorlist');
|
||||
$p.find('.preview').addClass('error-loading');
|
||||
$.each(errors, function(i, v) {
|
||||
$errors.append('<li>' + v + '</li>');
|
||||
});
|
||||
};
|
||||
function postUnsaved(data) {
|
||||
$('input[name="unsaved_data"]').val(JSON.stringify(data));
|
||||
}
|
||||
|
||||
$d.delegate('.reset', 'click', _pd(function() {
|
||||
var $this = $(this),
|
||||
$p = $this.closest('.row');
|
||||
$p.find('input[type="hidden"]').val('');
|
||||
$p.find('input[type=file], .note').show();
|
||||
$p.find('.preview').removeAttr('src').removeClass('loaded');
|
||||
updatePersona();
|
||||
$this.hide();
|
||||
var licensesByClass = {
|
||||
'copyr': {
|
||||
'id': 7,
|
||||
'name': gettext('All Rights Reserved')
|
||||
},
|
||||
'cc-attrib': {
|
||||
'id': 9,
|
||||
'name': gettext('Creative Commons Attribution 3.0'),
|
||||
'url': 'http://creativecommons.org/licenses/by/3.0/'
|
||||
},
|
||||
'cc-attrib cc-noncom': {
|
||||
'id': 10,
|
||||
'name': gettext('Creative Commons Attribution-NonCommercial 3.0'),
|
||||
'url': 'http://creativecommons.org/licenses/by-nc/3.0/'
|
||||
},
|
||||
'cc-attrib cc-noncom cc-noderiv': {
|
||||
'id': 11,
|
||||
'name': gettext('Creative Commons Attribution-NonCommercial-NoDerivs 3.0'),
|
||||
'url': 'http://creativecommons.org/licenses/by/3.0/'
|
||||
},
|
||||
'cc-attrib cc-noncom cc-share': {
|
||||
'id': 8,
|
||||
'name': gettext('Creative Commons Attribution-NonCommercial-Share Alike 3.0'),
|
||||
'url': 'http://creativecommons.org/licenses/by-nc-sa/3.0/'
|
||||
},
|
||||
'cc-attrib cc-noderiv': {
|
||||
'id': 12,
|
||||
'name': gettext('Creative Commons Attribution-NoDerivs 3.0'),
|
||||
'url': 'http://creativecommons.org/licenses/by-nd/3.0/'
|
||||
},
|
||||
'cc-attrib cc-share': {
|
||||
'id': 13,
|
||||
'name': gettext('Creative Commons Attribution-ShareAlike 3.0'),
|
||||
'url': 'http://creativecommons.org/licenses/by/3.0/'
|
||||
}
|
||||
};
|
||||
// Build an object for lookups by id: {{7: 'copyr'}, {9: 'cc-attrib'}, ...}.
|
||||
var licenseClassesById = _.object(_.map(licensesByClass, function(v, k) {
|
||||
return [v.id, k];
|
||||
}));
|
||||
|
||||
$d.delegate('input[type="file"]', 'upload_finished', upload_finished)
|
||||
.delegate('input[type="file"]', 'upload_start', upload_start)
|
||||
.delegate('input[type="file"]', 'upload_success', upload_success)
|
||||
.delegate('input[type="file"]', 'upload_errors', upload_errors)
|
||||
.delegate('input[type="file"]', 'change', function(e) {
|
||||
$(this).imageUploader();
|
||||
// Validate the form.
|
||||
var $form = $('#submit-persona form');
|
||||
$form.delegate('input, select, textarea', 'change keyup paste', function(e) {
|
||||
checkValid(e.target.form);
|
||||
});
|
||||
checkValid($form[0]);
|
||||
|
||||
function updatePersona() {
|
||||
var previewSrc = $('#persona-header .preview').attr('src'),
|
||||
$preview = $('#persona-preview .persona-viewer');
|
||||
if (previewSrc) {
|
||||
$preview.css('background-image', 'url(' + previewSrc + ')');
|
||||
} else {
|
||||
$preview.removeAttr('style');
|
||||
initLicense();
|
||||
initCharCount();
|
||||
initPreview();
|
||||
|
||||
function initLicense() {
|
||||
var $licenseField = $('#id_license');
|
||||
|
||||
function licenseUpdate(updateList) {
|
||||
var licenseClass;
|
||||
if ($('input[data-cc="copyr"]:checked').length) {
|
||||
licenseClass = 'copyr';
|
||||
} else {
|
||||
licenseClass = $('input[data-cc]:checked').map(function() {
|
||||
return $(this).data('cc');
|
||||
}).toArray().join(' ');
|
||||
}
|
||||
var license = licensesByClass[licenseClass];
|
||||
if (license) {
|
||||
var licenseTxt = license['name'];
|
||||
if (license['url']) {
|
||||
licenseTxt = format('<a href="{0}" target="_blank">{1}</a>',
|
||||
license['url'], licenseTxt);
|
||||
}
|
||||
var $p = $('#persona-license');
|
||||
$p.show().find('#cc-license').html(licenseTxt).attr('class', 'license icon ' + licenseClass);
|
||||
$licenseField.val(license['id']);
|
||||
if (updateList) {
|
||||
updateLicenseList();
|
||||
}
|
||||
}
|
||||
}
|
||||
var data = {'id': '0'};
|
||||
$.each(['name', 'accentcolor', 'textcolor'], function(i, v) {
|
||||
data[v] = $d.find('#id_' + v).val();
|
||||
});
|
||||
// TODO(cvan): We need to link to the CDN-served Persona images since
|
||||
// Personas cannot reference moz-filedata URIs.
|
||||
data['header'] = data['headerURL'] = $d.find('#persona-header .preview').attr('src');
|
||||
data['footer'] = data['footerURL'] = $d.find('#persona-footer .preview').attr('src');
|
||||
$preview.attr('data-browsertheme', JSON.stringify(data));
|
||||
var accentcolor = $d.find('#id_accentcolor').attr('data-rgb'),
|
||||
textcolor = $d.find('#id_textcolor').val();
|
||||
$preview.find('.title, .author').css({
|
||||
'background-color': format('rgba({0}, .7)', accentcolor),
|
||||
'color': textcolor
|
||||
});
|
||||
}
|
||||
|
||||
var $color = $('#submit-persona input[type=color]');
|
||||
$color.change(function() {
|
||||
var $this = $(this),
|
||||
val = $this.val();
|
||||
if (val.indexOf('#') === 0) {
|
||||
var rgb = hex2rgb(val);
|
||||
$this.attr('data-rgb', format('{0},{1},{2}', rgb.r, rgb.g, rgb.b));
|
||||
}
|
||||
updatePersona();
|
||||
}).trigger('change');
|
||||
|
||||
// Check for native `input[type=color]` support (i.e., WebKit).
|
||||
if ($color[0].type === 'color') {
|
||||
$('.miniColors-trigger').hide();
|
||||
} else {
|
||||
$color.miniColors({
|
||||
change: function() {
|
||||
$color.trigger('change');
|
||||
updatePersona();
|
||||
// Hide the other license options when the copyright license is selected.
|
||||
$form.delegate('input[name="cc-attrib"]', 'change', function() {
|
||||
var $noncc = $('.noncc');
|
||||
$noncc.toggleClass('disabled', $(this).data('cc') == 'copyr');
|
||||
if ($noncc.find('input[type=radio]:not(:checked)').length == 5) {
|
||||
$('input[name="cc-noncom"][value=1], input[name="cc-noderiv"][value=2]').prop('checked', true);
|
||||
}
|
||||
});
|
||||
$('input[data-cc="copyr"]').trigger('change');
|
||||
|
||||
// Whenever a radio field changes, update the license.
|
||||
$('input[name^="cc-"]').change(licenseUpdate);
|
||||
licenseUpdate();
|
||||
|
||||
if ($licenseField.val()) {
|
||||
$('input[type=radio][data-cc="' + licenseClassesById[$licenseField.val()] + '"]').prop('checked', true);
|
||||
licenseUpdate();
|
||||
}
|
||||
|
||||
$form.delegate('input[type=radio][name=license]', 'change', function() {
|
||||
// Upon selecting license from advanced menu, change it in the Q/A format.
|
||||
$('.noncc.disabled').removeClass('disabled');
|
||||
$('input[name^="cc-"]').prop('checked', false);
|
||||
$('input[type=radio][data-cc~="' + licenseClassesById[$(this).val()].split(' ') + '"]').prop('checked', true);
|
||||
licenseUpdate(false);
|
||||
});
|
||||
|
||||
function updateLicenseList() {
|
||||
$('#persona-license-list input[value="' + $licenseField.val() + '"]').prop('checked', true);
|
||||
}
|
||||
|
||||
$('#persona-license .select-license').click(_pd(function() {
|
||||
$('#persona-license-list').toggle();
|
||||
updateLicenseList();
|
||||
}));
|
||||
updateLicenseList();
|
||||
}
|
||||
|
||||
$('#id_name').bind('change keyup paste blur', _.throttle(function() {
|
||||
$('#persona-preview-name').text($(this).val() || gettext("Your Theme's Name"));
|
||||
slugify();
|
||||
}, 250)).trigger('change');
|
||||
$('#submit-persona').submit(function() {
|
||||
postUnsaved(POST);
|
||||
});
|
||||
var POST = {};
|
||||
|
||||
POST = loadUnsaved();
|
||||
_.each(POST, function(v, k) {
|
||||
$('input[value="' + k + '"]').siblings('input[type=file]').trigger('upload_success', [{dataURL: v}, k]);
|
||||
});
|
||||
}
|
||||
function initPreview() {
|
||||
var $d = $('#persona-design'),
|
||||
upload_finished = function(e) {
|
||||
$(this).closest('.row').find('.preview').removeClass('loading');
|
||||
$('#submit-persona button').attr('disabled', false);
|
||||
updatePersona();
|
||||
},
|
||||
upload_start = function(e, file) {
|
||||
var $p = $(this).closest('.row'),
|
||||
$errors = $p.find('.errorlist');
|
||||
if ($errors.length == 2) {
|
||||
$errors.eq(0).remove();
|
||||
}
|
||||
$p.find('.errorlist').html('');
|
||||
$p.find('.preview').addClass('loading').removeClass('error-loading');
|
||||
$('#submit-persona button').attr('disabled', true);
|
||||
},
|
||||
upload_success = function(e, file, upload_hash) {
|
||||
var $p = $(this).closest('.row');
|
||||
$p.find('input[type="hidden"]').val(upload_hash);
|
||||
$p.find('input[type=file], .note').hide();
|
||||
$p.find('.preview').attr('src', file.dataURL).addClass('loaded');
|
||||
POST[upload_hash] = file.dataURL; // Remember this as "posted" data.
|
||||
updatePersona();
|
||||
$p.find('.preview, .reset').show();
|
||||
},
|
||||
upload_errors = function(e, file, errors) {
|
||||
var $p = $(this).closest('.row'),
|
||||
$errors = $p.find('.errorlist');
|
||||
$p.find('.preview').addClass('error-loading');
|
||||
$.each(errors, function(i, v) {
|
||||
$errors.append('<li>' + v + '</li>');
|
||||
});
|
||||
};
|
||||
|
||||
$d.delegate('.reset', 'click', _pd(function() {
|
||||
var $this = $(this),
|
||||
$p = $this.closest('.row');
|
||||
$p.find('input[type="hidden"]').val('');
|
||||
$p.find('input[type=file], .note').show();
|
||||
$p.find('.preview').removeAttr('src').removeClass('loaded');
|
||||
updatePersona();
|
||||
$this.hide();
|
||||
}));
|
||||
|
||||
function postUnsaved(data) {
|
||||
$('input[name="unsaved_data"]').val(JSON.stringify(data));
|
||||
}
|
||||
$d.delegate('input[type="file"]', 'upload_finished', upload_finished)
|
||||
.delegate('input[type="file"]', 'upload_start', upload_start)
|
||||
.delegate('input[type="file"]', 'upload_success', upload_success)
|
||||
.delegate('input[type="file"]', 'upload_errors', upload_errors)
|
||||
.delegate('input[type="file"]', 'change', function(e) {
|
||||
$(this).imageUploader();
|
||||
});
|
||||
|
||||
function updatePersona() {
|
||||
var previewSrc = $('#persona-header .preview').attr('src'),
|
||||
$preview = $('#persona-preview .persona-viewer');
|
||||
if (previewSrc) {
|
||||
$preview.css('background-image', 'url(' + previewSrc + ')');
|
||||
} else {
|
||||
$preview.removeAttr('style');
|
||||
}
|
||||
var data = {'id': '0'};
|
||||
$.each(['name', 'accentcolor', 'textcolor'], function(i, v) {
|
||||
data[v] = $d.find('#id_' + v).val();
|
||||
});
|
||||
// TODO(cvan): We need to link to the CDN-served Persona images since
|
||||
// Personas cannot reference moz-filedata URIs.
|
||||
data['header'] = data['headerURL'] = $d.find('#persona-header .preview').attr('src');
|
||||
data['footer'] = data['footerURL'] = $d.find('#persona-footer .preview').attr('src');
|
||||
$preview.attr('data-browsertheme', JSON.stringify(data));
|
||||
var accentcolor = $d.find('#id_accentcolor').attr('data-rgb'),
|
||||
textcolor = $d.find('#id_textcolor').val();
|
||||
$preview.find('.title, .author').css({
|
||||
'background-color': format('rgba({0}, .7)', accentcolor),
|
||||
'color': textcolor
|
||||
});
|
||||
}
|
||||
|
||||
function loadUnsaved() {
|
||||
return JSON.parse($('input[name="unsaved_data"]').val() || '{}');
|
||||
}
|
||||
var $color = $('#submit-persona input[type=color]');
|
||||
$color.change(function() {
|
||||
var $this = $(this),
|
||||
val = $this.val();
|
||||
if (val.indexOf('#') === 0) {
|
||||
var rgb = hex2rgb(val);
|
||||
$this.attr('data-rgb', format('{0},{1},{2}', rgb.r, rgb.g, rgb.b));
|
||||
}
|
||||
updatePersona();
|
||||
}).trigger('change');
|
||||
|
||||
// Check for native `input[type=color]` support (i.e., WebKit).
|
||||
if ($color[0].type === 'color') {
|
||||
$('.miniColors-trigger').hide();
|
||||
} else {
|
||||
$color.miniColors({
|
||||
change: function() {
|
||||
$color.trigger('change');
|
||||
updatePersona();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#id_name').bind('change keyup paste blur', _.throttle(function() {
|
||||
$('#persona-preview-name').text($(this).val() || gettext("Your Theme's Name"));
|
||||
slugify();
|
||||
}, 250)).trigger('change');
|
||||
$('#submit-persona').submit(function() {
|
||||
postUnsaved(POST);
|
||||
});
|
||||
|
||||
POST = loadUnsaved();
|
||||
_.each(POST, function(v, k) {
|
||||
$('input[value="' + k + '"]').siblings('input[type=file]').trigger('upload_success', [{dataURL: v}, k]);
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
||||
|
|
|
@ -373,7 +373,7 @@ $(document).ready(function() {
|
|||
|
||||
$('.modal-delete').each(function() {
|
||||
var el = $(this);
|
||||
el.modal(el.closest('li').find('.delete-addon'), {
|
||||
el.modal(el.parent().find('.delete-addon'), {
|
||||
width: 400,
|
||||
callback: function(obj) {
|
||||
fixPasswordField(this);
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
update waffle_switch_amo set active = 1 where name = 'soft_delete';
|
Загрузка…
Ссылка в новой задаче