featured collections w/ admin tool (bug 635331)
This commit is contained in:
Родитель
8c2ea69c6a
Коммит
9ca694d42c
|
@ -16,6 +16,7 @@ class ES(object):
|
|||
self.in_ = {}
|
||||
self.or_ = {}
|
||||
self.queries = {}
|
||||
self.prefixes = {}
|
||||
self.fields = ['id']
|
||||
self.ordering = []
|
||||
self.start = 0
|
||||
|
@ -28,6 +29,7 @@ class ES(object):
|
|||
new.in_ = dict(self.in_)
|
||||
new.or_ = list(self.or_)
|
||||
new.queries = dict(self.queries)
|
||||
new.prefixes = dict(self.prefixes)
|
||||
new.fields = list(self.fields)
|
||||
new.ordering = list(self.ordering)
|
||||
new.start = self.start
|
||||
|
@ -50,7 +52,11 @@ class ES(object):
|
|||
|
||||
def query(self, **kw):
|
||||
new = self._clone()
|
||||
new.queries.update(kw)
|
||||
for key, value in kw.items():
|
||||
if key.endswith('__startswith'):
|
||||
new.prefixes[key.rstrip('__startswith')] = value
|
||||
else:
|
||||
new.queries[key] = value
|
||||
return new
|
||||
|
||||
def filter(self, **kw):
|
||||
|
@ -89,6 +95,8 @@ class ES(object):
|
|||
qs = {}
|
||||
if self.queries:
|
||||
qs['query'] = {'term': self.queries}
|
||||
if self.prefixes:
|
||||
qs.setdefault('query', {}).update({'prefix': self.prefixes})
|
||||
|
||||
if len(self.filters) + len(self.in_) + len(self.or_) > 1:
|
||||
qs['filter'] = {'and': []}
|
||||
|
|
|
@ -545,3 +545,16 @@ class RecommendedCollection(Collection):
|
|||
d[addon] += score
|
||||
addons = sorted(d.items(), key=lambda x: x[1], reverse=True)
|
||||
return [addon for addon, score in addons if addon not in addon_ids]
|
||||
|
||||
|
||||
class FeaturedCollection(amo.models.ModelBase):
|
||||
application = models.ForeignKey(Application)
|
||||
collection = models.ForeignKey(Collection)
|
||||
locale = models.CharField(max_length=10, default='', blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'featured_collections'
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s (%s: %s)' % (self.collection, self.application,
|
||||
self.locale)
|
||||
|
|
|
@ -4,7 +4,7 @@ import pyes.exceptions as pyes
|
|||
|
||||
|
||||
def extract(collection):
|
||||
attrs = ('id', 'name', 'slug', 'type', 'application_id')
|
||||
attrs = ('id', 'name', 'slug', 'author_username', 'type', 'application_id')
|
||||
d = dict(zip(attrs, attrgetter(*attrs)(collection)))
|
||||
d['app'] = d.pop('application_id')
|
||||
d['name'] = unicode(d['name']) # Coerce to unicode.
|
||||
|
|
|
@ -3,14 +3,18 @@ import re
|
|||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.forms.models import modelformset_factory
|
||||
from django.template import Context, Template, TemplateSyntaxError
|
||||
|
||||
import happyforms
|
||||
from tower import ugettext_lazy as _lazy
|
||||
from quieter_formset.formset import BaseModelFormSet
|
||||
|
||||
import amo
|
||||
import product_details
|
||||
from amo.urlresolvers import reverse
|
||||
from applications.models import Application, AppVersion
|
||||
from bandwagon.models import Collection, FeaturedCollection
|
||||
from zadmin.models import ValidationJob
|
||||
|
||||
|
||||
|
@ -107,3 +111,50 @@ class NotifyForm(happyforms.Form):
|
|||
|
||||
def clean_subject(self):
|
||||
return self.check_template(self.cleaned_data['subject'])
|
||||
|
||||
|
||||
class FeaturedCollectionForm(happyforms.ModelForm):
|
||||
LOCALES = (('', u'(Default Locale)'),) + tuple(
|
||||
(i, product_details.languages[i]['native'])
|
||||
for i in settings.AMO_LANGUAGES)
|
||||
|
||||
application = forms.ModelChoiceField(Application.objects.all())
|
||||
collection = forms.CharField(widget=forms.HiddenInput)
|
||||
locale = forms.ChoiceField(choices=LOCALES, required=False)
|
||||
|
||||
class Meta:
|
||||
model = FeaturedCollection
|
||||
fields = ('application', 'locale')
|
||||
|
||||
def clean_collection(self):
|
||||
application = self.cleaned_data.get('application', None)
|
||||
collection = self.cleaned_data.get('collection', None)
|
||||
if not Collection.objects.filter(id=collection,
|
||||
application=application).exists():
|
||||
raise forms.ValidationError(
|
||||
u'Invalid collection for this application.')
|
||||
return collection
|
||||
|
||||
def save(self, commit=False):
|
||||
collection = self.cleaned_data['collection']
|
||||
f = super(FeaturedCollectionForm, self).save(commit=commit)
|
||||
f.collection = Collection.objects.get(id=collection)
|
||||
f.save()
|
||||
return f
|
||||
|
||||
|
||||
class BaseFeaturedCollectionFormSet(BaseModelFormSet):
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(BaseFeaturedCollectionFormSet, self).__init__(*args, **kw)
|
||||
for form in self.initial_forms:
|
||||
try:
|
||||
form.initial['collection'] = (FeaturedCollection.objects
|
||||
.get(id=form.instance.id).collection.id)
|
||||
except (FeaturedCollection.DoesNotExist, Collection.DoesNotExist):
|
||||
form.initial['collection'] = None
|
||||
|
||||
|
||||
FeaturedCollectionFormSet = modelformset_factory(FeaturedCollection,
|
||||
form=FeaturedCollectionForm, formset=BaseFeaturedCollectionFormSet,
|
||||
can_delete=True, extra=0)
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<a href="{{ collection.get_url_path() }}" target="_blank"
|
||||
class="collectionitem {% if collection.all_personas %}personas-collection{% endif %}">
|
||||
{{ collection.name }}</a>
|
||||
<a href="#" class="replace">Replace with another collection</a>
|
|
@ -0,0 +1,79 @@
|
|||
{% extends "admin/base.html" %}
|
||||
|
||||
{% set title = 'Feature Manager' %}
|
||||
{% block title %}{{ page_title(title) }}{% endblock %}
|
||||
|
||||
{% block bodyattrs %}
|
||||
data-collections-url="{{ url('zadmin.collections_json') }}"
|
||||
data-featured-collection-url="{{ url('zadmin.featured_collection') }}"
|
||||
{% endblock %}
|
||||
|
||||
{% block extrahead %}
|
||||
<link rel="stylesheet" href="{{ media('css/zamboni/admin_features.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ super() }}
|
||||
<script src="{{ media('js/zamboni/admin_features.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% macro fc(form) %}
|
||||
{% set app_id = form.application.value()|int %}
|
||||
{% set app = amo.APPS_ALL.get(app_id, '') %}
|
||||
{% set collection_id = form.collection.value() %}
|
||||
{% set collection_disabled = 'disabled' if not (app_id or collection_id) and
|
||||
not form.collection.errors %}
|
||||
<tr data-app="{{ app.id }}">
|
||||
<td class="app {{ app.short }}">
|
||||
{{ form.id }}
|
||||
{{ form.application }}
|
||||
{{ form.application.errors }}
|
||||
</td>
|
||||
<td>
|
||||
{{ form.locale }}
|
||||
{{ form.locale.errors }}
|
||||
</td>
|
||||
<td class="collection{% if collection_id %} loading{% endif %}"
|
||||
data-collection="{{ collection_id }}">
|
||||
<div class="current-collection js-hidden"></div>
|
||||
<input type="text" class="placeholder collection-ac{% if collection_id %} js-hidden{% endif %}"
|
||||
{{ collection_disabled }} placeholder="Enter collection ID or name">
|
||||
{{ form.collection }}
|
||||
{{ form.collection.errors }}
|
||||
</td>
|
||||
<td>
|
||||
<span class="js-hidden delete">{{ form.DELETE }}{{ form.DELETE.label_tag() }}</span>
|
||||
<a href="#" class="remove">×</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endmacro %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{{ title }}</h2>
|
||||
<form action="" method="post">
|
||||
{{ csrf() }}
|
||||
{% include "messages.html" %}
|
||||
{{ form.non_form_errors() }}
|
||||
{{ form.management_form }}
|
||||
<table>
|
||||
<thead>
|
||||
<th>Application</th>
|
||||
<th>Locale</th>
|
||||
<th>Collection</th>
|
||||
<th class="js-hidden">Delete</th>
|
||||
</thead>
|
||||
<tbody id="features">
|
||||
{% for form in form.forms %}
|
||||
{{ fc(form) }}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot class="hidden">
|
||||
{{ fc(form.empty_form) }}
|
||||
</tfoot>
|
||||
</table>
|
||||
<p><a href="#" id="add">Add a Featured Collection</a></p>
|
||||
<p>
|
||||
<button type="submit">Save Changes</button> or <a href="">Cancel</a>
|
||||
</p>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -8,6 +8,7 @@
|
|||
('Celery', url('zadmin.celery'), 'See celery stats'),
|
||||
('Elasticseach', url('zadmin.jetpack'), 'Manage elasticsearch'),
|
||||
('Env', url('amo.env'), 'See the request environment'),
|
||||
('Features', url('zadmin.features'), 'Manage featured add-ons'),
|
||||
('Flagged', url('zadmin.flagged'), 'See flagged reviews'),
|
||||
('Hera', url('zadmin.hera'), 'Purge pages from zeus'),
|
||||
('Jetpack', url('zadmin.jetpack'), 'Upgrade jetpack add-ons'),
|
||||
|
|
|
@ -15,16 +15,18 @@ from pyquery import PyQuery as pq
|
|||
import test_utils
|
||||
|
||||
import amo
|
||||
from amo.tests import close_to_now, assert_no_validation_errors
|
||||
from amo.tests import (formset, initial, close_to_now,
|
||||
assert_no_validation_errors)
|
||||
from amo.urlresolvers import reverse
|
||||
from addons.models import Addon
|
||||
from applications.models import AppVersion
|
||||
from bandwagon.models import Collection, FeaturedCollection
|
||||
from devhub.models import ActivityLog
|
||||
from files.models import Approval, File
|
||||
from users.models import UserProfile
|
||||
from users.utils import get_task_user
|
||||
from versions.models import ApplicationsVersions, Version
|
||||
from zadmin.forms import NotifyForm
|
||||
from zadmin.forms import NotifyForm, FeaturedCollectionForm
|
||||
from zadmin.models import ValidationJob, ValidationResult, EmailPreviewTopic
|
||||
from zadmin.views import completed_versions_dirty, find_files
|
||||
from zadmin import tasks
|
||||
|
@ -923,3 +925,122 @@ class TestEmailPreview(test_utils.TestCase):
|
|||
eq_(rdr.next(), ['from_email', 'recipient_list', 'subject', 'body'])
|
||||
eq_(rdr.next(), ['admin@mozilla.org', 'funnyguy@mozilla.org',
|
||||
'the subject', 'Hello Ivan Krsti\xc4\x87'])
|
||||
|
||||
|
||||
class TestFeatures(test_utils.TestCase):
|
||||
fixtures = ['base/apps', 'base/users', 'base/collections']
|
||||
|
||||
def setUp(self):
|
||||
assert self.client.login(username='admin@mozilla.com',
|
||||
password='password')
|
||||
self.url = reverse('zadmin.features')
|
||||
FeaturedCollection.objects.create(application_id=amo.FIREFOX.id,
|
||||
locale='zh-CN', collection_id=80)
|
||||
self.f = self.client.get(self.url).context['form'].initial_forms[0]
|
||||
self.initial = self.f.initial
|
||||
|
||||
def test_form_initial(self):
|
||||
eq_(self.initial['application'], amo.FIREFOX.id)
|
||||
eq_(self.initial['locale'], 'zh-CN')
|
||||
eq_(self.initial['collection'], 80)
|
||||
|
||||
def test_form_attrs(self):
|
||||
r = self.client.get(self.url)
|
||||
eq_(r.status_code, 200)
|
||||
doc = pq(r.content)
|
||||
eq_(doc('#features tr').attr('data-app'), str(amo.FIREFOX.id))
|
||||
assert doc('#features td.app').hasClass(amo.FIREFOX.short)
|
||||
eq_(doc('#features td.collection.loading').attr('data-collection'),
|
||||
'80')
|
||||
assert doc('#features .collection-ac.js-hidden')
|
||||
assert not doc('#features .collection-ac[disabled]')
|
||||
|
||||
def test_no_app_disabled_autocomplete(self):
|
||||
"""If no application, autocomplete field should be disabled."""
|
||||
data = formset(self.initial, {}, initial_count=1)
|
||||
r = self.client.post(self.url, data)
|
||||
doc = pq(r.content)
|
||||
assert doc('#features .collection-ac[disabled]')
|
||||
|
||||
def test_disabled_autocomplete_errors(self):
|
||||
"""If any collection errors, autocomplete field should be enabled."""
|
||||
d = dict(application=amo.FIREFOX.id, collection=999)
|
||||
data = formset(self.initial, d, initial_count=1)
|
||||
r = self.client.post(self.url, data)
|
||||
doc = pq(r.content)
|
||||
assert not doc('#features .collection-ac[disabled]')
|
||||
|
||||
def test_required_app(self):
|
||||
d = dict(locale='zh-CN', collection=80)
|
||||
data = formset(self.initial, d, initial_count=1)
|
||||
r = self.client.post(self.url, data)
|
||||
eq_(r.status_code, 200)
|
||||
eq_(r.context['form'].errors[0]['application'],
|
||||
['This field is required.'])
|
||||
eq_(r.context['form'].errors[0]['collection'],
|
||||
['Invalid collection for this application.'])
|
||||
|
||||
def test_bad_app(self):
|
||||
d = dict(application=999, collection=80)
|
||||
data = formset(self.initial, d, initial_count=1)
|
||||
r = self.client.post(self.url, data)
|
||||
eq_(r.context['form'].errors[0]['application'],
|
||||
['Select a valid choice. That choice is not one of the available '
|
||||
'choices.'])
|
||||
|
||||
def test_bad_collection_for_app(self):
|
||||
d = dict(application=amo.THUNDERBIRD.id, collection=80)
|
||||
data = formset(self.initial, d, initial_count=1)
|
||||
r = self.client.post(self.url, data)
|
||||
eq_(r.context['form'].errors[0]['collection'],
|
||||
['Invalid collection for this application.'])
|
||||
|
||||
def test_optional_locale(self):
|
||||
d = dict(application=amo.FIREFOX.id, collection=80)
|
||||
data = formset(self.initial, d, initial_count=1)
|
||||
r = self.client.post(self.url, data)
|
||||
eq_(r.context['form'].errors, [{}])
|
||||
|
||||
def test_bad_locale(self):
|
||||
d = dict(application=amo.FIREFOX.id, locale='klingon', collection=80)
|
||||
data = formset(self.initial, d, initial_count=1)
|
||||
r = self.client.post(self.url, data)
|
||||
eq_(r.context['form'].errors[0]['locale'],
|
||||
['Select a valid choice. klingon is not one of the available '
|
||||
'choices.'])
|
||||
|
||||
def test_required_collection(self):
|
||||
d = dict(application=amo.FIREFOX.id)
|
||||
data = formset(self.initial, d, initial_count=1)
|
||||
r = self.client.post(self.url, data)
|
||||
eq_(r.context['form'].errors[0]['collection'],
|
||||
['This field is required.'])
|
||||
|
||||
def test_bad_collection(self):
|
||||
d = dict(application=amo.FIREFOX.id, collection=999)
|
||||
data = formset(self.initial, d, initial_count=1)
|
||||
r = self.client.post(self.url, data)
|
||||
eq_(r.context['form'].errors[0]['collection'],
|
||||
['Invalid collection for this application.'])
|
||||
|
||||
def test_success_insert(self):
|
||||
dupe = initial(self.f)
|
||||
del dupe['id']
|
||||
dupe.update(locale='fr')
|
||||
data = formset(initial(self.f), dupe, initial_count=1)
|
||||
r = self.client.post(self.url, data)
|
||||
eq_(FeaturedCollection.objects.count(), 2)
|
||||
eq_(FeaturedCollection.objects.all()[1].locale, 'fr')
|
||||
|
||||
def test_success_update(self):
|
||||
d = initial(self.f)
|
||||
d.update(locale='fr')
|
||||
r = self.client.post(self.url, formset(d, initial_count=1))
|
||||
eq_(r.status_code, 302)
|
||||
eq_(FeaturedCollection.objects.all()[0].locale, 'fr')
|
||||
|
||||
def test_success_delete(self):
|
||||
d = initial(self.f)
|
||||
d.update(DELETE=True)
|
||||
r = self.client.post(self.url, formset(d, initial_count=1))
|
||||
eq_(FeaturedCollection.objects.count(), 0)
|
||||
|
|
|
@ -32,6 +32,13 @@ urlpatterns = patterns('',
|
|||
url(r'^email_preview/(?P<topic>.*)\.csv$',
|
||||
views.email_preview_csv, name='zadmin.email_preview_csv'),
|
||||
url(r'^jetpack$', views.jetpack, name='zadmin.jetpack'),
|
||||
|
||||
url('^features$', views.features, name='zadmin.features'),
|
||||
url('^features/collections\.json$', views.es_collections_json,
|
||||
name='zadmin.collections_json'),
|
||||
url('^features/featured-collection$', views.featured_collection,
|
||||
name='zadmin.featured_collection'),
|
||||
|
||||
url('^elastic$', views.elastic, name='zadmin.elastic'),
|
||||
url('^mail$', views.mail, name='zadmin.mail'),
|
||||
url('^celery$', views.celery, name='zadmin.celery'),
|
||||
|
|
|
@ -33,11 +33,12 @@ from amo.urlresolvers import reverse
|
|||
from amo.utils import chunked, sorted_groupby
|
||||
from addons.models import Addon
|
||||
from addons.utils import ReverseNameLookup
|
||||
from bandwagon.models import Collection
|
||||
from files.models import Approval, File
|
||||
from versions.models import Version
|
||||
|
||||
from . import tasks
|
||||
from .forms import BulkValidationForm, NotifyForm
|
||||
from .forms import BulkValidationForm, NotifyForm, FeaturedCollectionFormSet
|
||||
from .models import ValidationJob, EmailPreviewTopic, Config
|
||||
|
||||
log = commonware.log.getLogger('z.zadmin')
|
||||
|
@ -315,6 +316,51 @@ def jetpack(request):
|
|||
upgrader=upgrader, by_version=by_version))
|
||||
|
||||
|
||||
@login_required
|
||||
@json_view
|
||||
def es_collections_json(request):
|
||||
app = request.GET.get('app', '')
|
||||
q = request.GET.get('q', '')
|
||||
qs = Collection.search()
|
||||
try:
|
||||
qs = qs.query(id__startswith=int(q))
|
||||
except ValueError:
|
||||
qs = qs.query(name__startswith=q)
|
||||
try:
|
||||
qs = qs.filter(app=int(app))
|
||||
except ValueError:
|
||||
pass
|
||||
data = []
|
||||
for c in qs[:7]:
|
||||
data.append({'id': c.id,
|
||||
'name': unicode(c.name),
|
||||
'all_personas': c.all_personas,
|
||||
'url': c.get_url_path()})
|
||||
return data
|
||||
|
||||
|
||||
@post_required
|
||||
@admin.site.admin_view
|
||||
def featured_collection(request):
|
||||
try:
|
||||
pk = int(request.POST.get('collection', 0))
|
||||
except ValueError:
|
||||
pk = 0
|
||||
c = get_object_or_404(Collection, pk=pk)
|
||||
return jingo.render(request, 'zadmin/featured_collection.html',
|
||||
dict(collection=c))
|
||||
|
||||
|
||||
@admin.site.admin_view
|
||||
def features(request):
|
||||
form = FeaturedCollectionFormSet(request.POST or None)
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
form.save(commit=False)
|
||||
messages.success(request, 'Changes successfully saved.')
|
||||
return redirect('zadmin.features')
|
||||
return jingo.render(request, 'zadmin/features.html', dict(form=form))
|
||||
|
||||
|
||||
@admin.site.admin_view
|
||||
def elastic(request):
|
||||
INDEX = site_settings.ES_INDEX
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
#add {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#features {
|
||||
border: 1px solid #666;
|
||||
border-width: 1px 0;
|
||||
}
|
||||
|
||||
#features tr:not(:last-child) {
|
||||
border-bottom: 1px dotted #aaa;
|
||||
}
|
||||
|
||||
#features td:last-child {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#features td.app {
|
||||
background-position: 0 5px;
|
||||
background-size: auto 24px;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
min-height: 24px;
|
||||
padding-left: 35px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#features td.collection {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
#features .replace {
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
#features .replace:link,
|
||||
#features .replace:visited {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#features input.collection-ac {
|
||||
width: 16em;
|
||||
}
|
||||
|
||||
#features .loading {
|
||||
background: url(../../img/zamboni/loading-white.gif) 50% 50% no-repeat;
|
||||
}
|
||||
|
||||
#features .collectionitem {
|
||||
background: url(../../img/icons/icons.png) 0 -300px no-repeat;
|
||||
display: inline-block;
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
#features .collectionitem.personas-collection {
|
||||
background: url(../../img/illustrations/themes.gif) no-repeat;
|
||||
background-size: 18px;
|
||||
}
|
||||
|
||||
.ui-autocomplete a b {
|
||||
color: #999;
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
#features a.remove {
|
||||
border-radius: 20px;
|
||||
background-color: #ddd;
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
height: 18px;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
#features tr:hover a.remove {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
#features tr:hover a.remove:hover {
|
||||
background-color: #2a4364;
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
$(document).ready(function(){
|
||||
function incTotalForms() {
|
||||
var $totalForms = $('#id_form-TOTAL_FORMS'),
|
||||
num = parseInt($totalForms.val()) + 1;
|
||||
$totalForms.val(num);
|
||||
return num;
|
||||
}
|
||||
|
||||
// Populate cells with current collections.
|
||||
$('#features td.collection').each(function() {
|
||||
var $td = $(this),
|
||||
cid = $td.attr('data-collection'),
|
||||
$input = $td.find('.collection-ac');
|
||||
if (!cid) {
|
||||
$td.removeClass('loading');
|
||||
$input.show();
|
||||
return;
|
||||
}
|
||||
$.post(document.body.getAttribute('data-featured-collection-url'),
|
||||
{'collection': cid}, function(data) {
|
||||
$td.removeClass('loading');
|
||||
$input.hide();
|
||||
$td.find('.current-collection').html(data).show();
|
||||
});
|
||||
});
|
||||
|
||||
$('#features').delegate('.app select', 'change', function() {
|
||||
// Update application id and toggle disabled attr on autocomplete field.
|
||||
var $this = $(this),
|
||||
$tr = $this.closest('tr'),
|
||||
val = $this.val();
|
||||
$tr.attr('data-app', val);
|
||||
$tr.find('.collection-ac').attr('disabled', !val);
|
||||
});
|
||||
$('#features').delegate('.remove', 'click', _pd(function() {
|
||||
$(this).closest('tr').hide();
|
||||
$(this).closest('td').find('input').attr('checked', true);
|
||||
}));
|
||||
$('#features').delegate('.replace', 'click', _pd(function() {
|
||||
var $td = $(this).closest('td');
|
||||
$td.find('.collection-ac').show();
|
||||
$td.find('input[type=hidden]').val('');
|
||||
$(this).parent().html('');
|
||||
})).delegate('.collection-ac', 'collectionAdd', function() {
|
||||
// Autocomplete for collection add form.
|
||||
var $input = $(this),
|
||||
$tr = $input.closest('tr'),
|
||||
$td = $input.closest('td'),
|
||||
$select = $tr.find('.collection-select');
|
||||
function selectCollection() {
|
||||
var item = JSON.parse($input.attr('data-item'));
|
||||
if (item) {
|
||||
$td.find('.errorlist').remove();
|
||||
var current = template(
|
||||
'<a href="{url}" target="_blank" ' +
|
||||
'class="collectionitem {is_personas}">{name}</a>' +
|
||||
'<a href="#" class="replace">Replace with another collection</a>'
|
||||
);
|
||||
$td.find('.current-collection').show().html(current({
|
||||
url: item.url,
|
||||
is_personas: item.all_personas ? 'personas-collection' : '',
|
||||
name: item.name
|
||||
}));
|
||||
$td.find('input[type=hidden]').val(item.id);
|
||||
$td.attr('data-collection', item.id);
|
||||
}
|
||||
$input.val('');
|
||||
$input.hide();
|
||||
}
|
||||
$input.autocomplete({
|
||||
minLength: 3,
|
||||
width: 300,
|
||||
source: function(request, response) {
|
||||
$.getJSON(document.body.getAttribute('data-collections-url'),
|
||||
{'app': $input.closest('tr').attr('data-app'),
|
||||
'q': request.term}, response);
|
||||
},
|
||||
focus: function(event, ui) {
|
||||
$input.val(ui.item.name);
|
||||
return false;
|
||||
},
|
||||
select: function(event, ui) {
|
||||
$input.val(ui.item.name).attr('data-item', JSON.stringify(ui.item));
|
||||
selectCollection();
|
||||
return false;
|
||||
}
|
||||
}).data('autocomplete')._renderItem = function(ul, item) {
|
||||
var html = format('<a>{0}<b>ID: {1}</b></a>', [item.name, item.id]);
|
||||
return $('<li>').data('item.autocomplete', item).append(html).appendTo(ul);
|
||||
};
|
||||
});
|
||||
|
||||
$('#features .collection-ac').trigger('collectionAdd');
|
||||
|
||||
$('#add').click(_pd(function() {
|
||||
var formId = incTotalForms() - 1,
|
||||
emptyForm = $('tfoot').html().replace(/__prefix__/g, formId);
|
||||
$('tbody').append(emptyForm);
|
||||
$('tbody tr:last-child .collection-ac').trigger('collectionAdd');
|
||||
}));
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
DROP TABLE IF EXISTS `featured_collections`;
|
||||
CREATE TABLE `featured_collections` (
|
||||
`id` int(11) unsigned AUTO_INCREMENT NOT NULL PRIMARY KEY,
|
||||
`created` datetime NOT NULL,
|
||||
`modified` datetime NOT NULL,
|
||||
`application_id` int(11) unsigned NOT NULL,
|
||||
`collection_id` int(11) unsigned NOT NULL,
|
||||
`locale` varchar(10)
|
||||
);
|
||||
|
||||
ALTER TABLE `featured_collections`
|
||||
ADD CONSTRAINT FOREIGN KEY (`application_id`)
|
||||
REFERENCES `applications` (`id`);
|
||||
ALTER TABLE `featured_collections`
|
||||
ADD CONSTRAINT FOREIGN KEY (`collection_id`)
|
||||
REFERENCES `collections` (`id`);
|
||||
|
||||
CREATE INDEX `application_id_idx` ON `featured_collections` (`application_id`);
|
||||
CREATE INDEX `collection_id_idx` ON `featured_collections` (`collection_id`);
|
Загрузка…
Ссылка в новой задаче