зеркало из https://github.com/mozilla/FlightDeck.git
Merge branch 'bug-679219-search_times_depended_on'
This commit is contained in:
Коммит
107b8f700f
|
@ -0,0 +1,24 @@
|
|||
from django import forms
|
||||
from django.forms.util import ErrorDict
|
||||
|
||||
class CleanForm(forms.Form):
|
||||
|
||||
def full_clean(self):
|
||||
"""
|
||||
Cleans self.data and populates self._errors and self.cleaned_data.
|
||||
|
||||
Does not remove cleaned_data if there are errors.
|
||||
"""
|
||||
self._errors = ErrorDict()
|
||||
if not self.is_bound: # Stop further processing.
|
||||
return
|
||||
|
||||
self.cleaned_data = {}
|
||||
# If the form is permitted to be empty, and none of the form data
|
||||
# has changed from the initial data, short circuit any validation.
|
||||
if self.empty_permitted and not self.has_changed():
|
||||
return
|
||||
self._clean_fields()
|
||||
self._clean_form()
|
||||
self._post_clean()
|
||||
|
|
@ -1033,7 +1033,9 @@ class PackageRevision(BaseModel):
|
|||
if save:
|
||||
# save as new version
|
||||
self.save()
|
||||
return self.dependencies.add(dep)
|
||||
ret = self.dependencies.add(dep)
|
||||
dep.package.refresh_index()
|
||||
return ret
|
||||
|
||||
def compare_dependency_conflicts(self, dep, as_upgrade=False):
|
||||
"""
|
||||
|
@ -1117,7 +1119,9 @@ class PackageRevision(BaseModel):
|
|||
'dependency (%s) removed' % dep.name)
|
||||
# save as new version
|
||||
self.save()
|
||||
return self.dependencies.remove(dep)
|
||||
ret = self.dependencies.remove(dep)
|
||||
dep.package.refresh_index()
|
||||
return ret
|
||||
raise DependencyException(
|
||||
'There is no such library in this %s' \
|
||||
% self.package.get_type_name())
|
||||
|
@ -1740,6 +1744,11 @@ class Package(BaseModel, SearchMixin):
|
|||
except PackageRevision.DoesNotExist:
|
||||
pass
|
||||
|
||||
if self.is_library():
|
||||
data['times_depended'] = (Package.objects
|
||||
.filter(latest__dependencies__in=self.revisions.all())
|
||||
.count())
|
||||
|
||||
try:
|
||||
es.index(data, settings.ES_INDEX, self._meta.db_table, id=self.id,
|
||||
bulk=bulk)
|
||||
|
|
|
@ -1,34 +1,18 @@
|
|||
from django import forms
|
||||
from django.forms.util import ErrorDict
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from base.forms import CleanForm
|
||||
|
||||
TYPE_CHOICES = (
|
||||
('l', 'Libraries'),
|
||||
('a', 'Add-ons'),
|
||||
)
|
||||
|
||||
class SearchForm(forms.Form):
|
||||
class SearchForm(CleanForm):
|
||||
q = forms.CharField(required=False)
|
||||
page = forms.IntegerField(required=False, initial=1)
|
||||
type = forms.ChoiceField(required=False, choices=TYPE_CHOICES)
|
||||
author = forms.ModelChoiceField(required=False, queryset=User.objects.all())
|
||||
copies = forms.IntegerField(required=False, initial=0)
|
||||
used = forms.IntegerField(required=False, initial=0)
|
||||
|
||||
def full_clean(self):
|
||||
"""
|
||||
Cleans self.data and populates self._errors and self.cleaned_data.
|
||||
|
||||
Does not remove cleaned_data if there are errors.
|
||||
"""
|
||||
self._errors = ErrorDict()
|
||||
if not self.is_bound: # Stop further processing.
|
||||
return
|
||||
|
||||
self.cleaned_data = {}
|
||||
# If the form is permitted to be empty, and none of the form data
|
||||
# has changed from the initial data, short circuit any validation.
|
||||
if self.empty_permitted and not self.has_changed():
|
||||
return
|
||||
self._clean_fields()
|
||||
self._clean_form()
|
||||
self._post_clean()
|
||||
|
|
|
@ -51,5 +51,17 @@
|
|||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if max_times_depended %}
|
||||
<li id="UsedFilter">
|
||||
Used by
|
||||
<span class="slider-value">{{ query.used }}</span>
|
||||
or more packages:
|
||||
<div class="slider">
|
||||
<div class="knob"></div>
|
||||
<span class="range start">0</span>
|
||||
<span class="range end">{{ max_times_depended }}</span>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</section>
|
||||
|
|
|
@ -112,6 +112,22 @@ class TestSearch(ESTestCase):
|
|||
|
||||
|
||||
class PackageSearchTest(ESTestCase):
|
||||
fixtures = ('mozilla_user', 'users', 'core_sdk',)
|
||||
|
||||
def test_times_depended_on(self):
|
||||
foo = create_library('foooooo')
|
||||
bar = create_addon('barrrr')
|
||||
|
||||
bar.latest.dependency_add(foo.latest)
|
||||
|
||||
self.es.refresh()
|
||||
|
||||
qs = Package.search().filter(times_depended__gte=1)
|
||||
|
||||
eq_(len(qs), 1)
|
||||
eq_(qs[0], foo)
|
||||
|
||||
class PackageHelperSearchTest(ESTestCase):
|
||||
"""
|
||||
search.helpers.package_search has some built-in sane defaults when
|
||||
searching for Packages.
|
||||
|
|
|
@ -28,16 +28,27 @@ def search(request):
|
|||
author = query.get('author')
|
||||
if author:
|
||||
filters['author'] = author.id
|
||||
|
||||
if query.get('copies'):
|
||||
filters['copies_count__gte'] = query['copies']
|
||||
else:
|
||||
query['copies'] = 0
|
||||
|
||||
if query.get('used'):
|
||||
filters['times_depended__gte'] = query['used']
|
||||
else:
|
||||
query['used'] = 0
|
||||
|
||||
|
||||
results = {}
|
||||
facets = {}
|
||||
|
||||
copies_facet = {'terms': {'field': 'copies_count'}}
|
||||
times_depended_facet = {'terms': {'field': 'times_depended'}}
|
||||
facets_ = {'copies': copies_facet, 'times_depended': times_depended_facet}
|
||||
if type_:
|
||||
filters['type'] = type_
|
||||
qs = package_search(q, **filters).facet(copies={'terms':
|
||||
{'field':'copies_count'}})
|
||||
qs = package_search(q, **filters).facet(**facets_)
|
||||
try:
|
||||
results['pager'] = Paginator(qs, per_page=limit).page(page)
|
||||
except EmptyPage:
|
||||
|
@ -48,8 +59,9 @@ def search(request):
|
|||
else:
|
||||
# combined view
|
||||
results['addons'] = package_search(q, type='a', **filters).facet(
|
||||
copies={'terms':{'field':'copies_count'}})[:5]
|
||||
results['libraries'] = package_search(q, type='l', **filters)[:5]
|
||||
**facets_)[:5]
|
||||
results['libraries'] = package_search(q, type='l', **filters).facet(
|
||||
**facets_)[:5]
|
||||
facets = _facets(results['addons'].facets)
|
||||
facets['everyone_total'] = facets['combined_total']
|
||||
template = 'aggregate.html'
|
||||
|
@ -101,10 +113,19 @@ def _facets(facets):
|
|||
max_ = copies_steps.pop()
|
||||
max_copies = max(max_copies, max_)
|
||||
|
||||
max_times_depended = 0
|
||||
if 'times_depended' in facets:
|
||||
depended_steps = [t['term'] for t in facets['times_depended']]
|
||||
if depended_steps:
|
||||
depended_steps.sort()
|
||||
max_ = depended_steps.pop()
|
||||
max_times_depended = max(max_times_depended, max_)
|
||||
|
||||
return {
|
||||
'addon_total': type_totals.get('a', 0),
|
||||
'library_total': type_totals.get('l', 0),
|
||||
'my_total': my_total,
|
||||
'combined_total': type_totals.get('a', 0) + type_totals.get('l', 0),
|
||||
'max_copies': max_copies,
|
||||
'max_times_depended': max_times_depended
|
||||
}
|
||||
|
|
|
@ -44,13 +44,16 @@ var SearchResult = new Class({
|
|||
newSidebar.replaces(sidebar);
|
||||
}
|
||||
|
||||
if (!this.slider) {
|
||||
this.slider = SearchResult.setupUI();
|
||||
if (!this.ui) {
|
||||
SearchResult.setupUI(this);
|
||||
} else {
|
||||
this.slider.sanityCheck = false;
|
||||
var loc = new URI(this.url);
|
||||
this.slider.set(loc.getData('copies') || 0);
|
||||
this.slider.sanityCheck = true;
|
||||
Object.each(this.ui.sliders, function(slider, name) {
|
||||
slider.sanityCheck = false;
|
||||
slider.set(loc.getData(name) || 0);
|
||||
slider.sanityCheck = true;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
@ -80,42 +83,51 @@ SearchResult.page = function(url) {
|
|||
SearchResult.fetch(String(window.location));
|
||||
};
|
||||
|
||||
SearchResult.setupUI = function() {
|
||||
var copies = $('CopiesFilter');
|
||||
if (copies) {
|
||||
var cSlider = copies.getElement('.slider'),
|
||||
cKnob = cSlider.getElement('.knob'),
|
||||
cValue = copies.getElement('.slider-value'),
|
||||
cRangeEnd = cSlider.getElement('.range.end'),
|
||||
end = cRangeEnd.get('text').toInt();
|
||||
SearchResult.setupUI = function(result) {
|
||||
var ui = { sliders: {} };
|
||||
if (result) result.ui = ui;
|
||||
|
||||
var initialStep = Math.max(0, cValue.get('text').toInt() || 0);
|
||||
|
||||
var copiesSlider = new Slider(cSlider, cKnob, {
|
||||
//snap: true,
|
||||
range: [0, end],
|
||||
initialStep: initialStep,
|
||||
onChange: function(step) {
|
||||
cValue.set('text', step);
|
||||
},
|
||||
onComplete: function(step) {
|
||||
if (!this.sanityCheck) return;
|
||||
var filters = ['Copies', 'Used'];
|
||||
filters.forEach(function(filter) {
|
||||
var container = $(filter + 'Filter'),
|
||||
dataKey = filter.toLowerCase();
|
||||
|
||||
var loc = new URI(String(window.location));
|
||||
loc.setData('copies', step);
|
||||
SearchResult.page(loc);
|
||||
}
|
||||
});
|
||||
// onComplete gets triggered many times when no dragging
|
||||
// actually occurred, because we set a range and initialStep. To
|
||||
// prevent those fake onComplete's from trigger anything, we
|
||||
// check our sanity by stopping all onComplete's that happen
|
||||
// during initialization, since sanityCheck get's set to true
|
||||
// _after_ construction.
|
||||
copiesSlider.sanityCheck = true;
|
||||
if (container) {
|
||||
|
||||
var sliderEl = container.getElement('.slider'),
|
||||
knobEl = sliderEl.getElement('.knob'),
|
||||
valueEl = container.getElement('.slider-value'),
|
||||
rangeEndEl = sliderEl.getElement('.range.end'),
|
||||
end = rangeEndEl.get('text').toInt();
|
||||
|
||||
return copiesSlider;
|
||||
}
|
||||
var initialStep = Math.max(0, valueEl.get('text').toInt() || 0);
|
||||
|
||||
var slider = new Slider(sliderEl, knobEl, {
|
||||
//snap: true,
|
||||
range: [0, end],
|
||||
initialStep: initialStep,
|
||||
onChange: function(step) {
|
||||
valueEl.set('text', step);
|
||||
},
|
||||
onComplete: function(step) {
|
||||
if (!this.sanityCheck) return;
|
||||
|
||||
var loc = new URI(String(window.location));
|
||||
loc.setData(dataKey, step);
|
||||
SearchResult.page(loc);
|
||||
}
|
||||
});
|
||||
// onComplete gets triggered many times when no dragging
|
||||
// actually occurred, because we set a range and initialStep. To
|
||||
// prevent those fake onComplete's from trigger anything, we
|
||||
// check our sanity by stopping all onComplete's that happen
|
||||
// during initialization, since sanityCheck get's set to true
|
||||
// _after_ construction.
|
||||
slider.sanityCheck = true;
|
||||
|
||||
ui.sliders[dataKey] = slider;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче