]> git.openstreetmap.org Git - osqa.git/commitdiff
Several improvements in the node bulk management feature. Improved filters and added...
authorhernani <hernani@0cfe37f9-358a-4d5e-be75-b63607b5c754>
Thu, 9 Sep 2010 15:55:47 +0000 (15:55 +0000)
committerhernani <hernani@0cfe37f9-358a-4d5e-be75-b63607b5c754>
Thu, 9 Sep 2010 15:55:47 +0000 (15:55 +0000)
git-svn-id: http://svn.osqa.net/svnroot/osqa/trunk@584 0cfe37f9-358a-4d5e-be75-b63607b5c754

13 files changed:
forum/forms/admin.py
forum/models/node.py
forum/skins/default/media/js/osqa.admin.js
forum/skins/default/media/js/osqa.main.js
forum/skins/default/media/style/djstyle_admin.css
forum/skins/default/templates/osqaadmin/nodeman.html
forum/urls.py
forum/utils/pagination.py
forum/views/admin.py
forum/views/commands.py
forum_modules/pgfulltext/handlers.py
forum_modules/pgfulltext/pg_fts_install.sql
forum_modules/pgfulltext/startup.py

index 389e70e450b3ae531427166d70d22dd604220c78..32d710bebc0a1f6d1f43a896a15c2065b9ae712c 100644 (file)
@@ -1,9 +1,11 @@
 import socket
 from django import forms
 from django.utils.translation import ugettext as _
 import socket
 from django import forms
 from django.utils.translation import ugettext as _
+from django.contrib.admin.widgets import FilteredSelectMultiple, AdminDateWidget
 from qanda import TitleField, EditorField
 from forum import settings
 from forum.models.node import NodeMetaClass
 from qanda import TitleField, EditorField
 from forum import settings
 from forum.models.node import NodeMetaClass
+from forum.models import User
 
 class IPListField(forms.CharField):
     def clean(self, value):
 
 class IPListField(forms.CharField):
     def clean(self, value):
index 0f8f5d3b141e1db2ef9feeb8ba43e6ff268327b5..ea83e7f2a24afd27bda25122e4697e8e85ec9797 100644 (file)
@@ -259,6 +259,10 @@ class Node(BaseModel, NodeContent):
 
         return nis
 
 
         return nis
 
+    @property
+    def state_list(self):
+        return [s.state_type for s in self.states.all()]
+
     @property
     def deleted(self):
         return self.nis.deleted
     @property
     def deleted(self):
         return self.nis.deleted
@@ -373,6 +377,18 @@ class Node(BaseModel, NodeContent):
                 tag.add_to_usage_count(1)
                 tag.save()
 
                 tag.add_to_usage_count(1)
                 tag.save()
 
+    def delete(self, *args, **kwargs):
+        self.active_revision = None
+        self.save()
+
+        for n in self.children.all():
+            n.delete()
+
+        for a in self.actions.all():
+            a.cancel()
+
+        super(Node, self).delete(*args, **kwargs)
+
     def save(self, *args, **kwargs):
         tags_changed = self._process_changes_in_tags()
 
     def save(self, *args, **kwargs):
         tags_changed = self._process_changes_in_tags()
 
index b7e6a1c2395462fc40466577bd44860cb79e9eb3..fb2bd0d66e49d86996b117fec9f929e0951d0829 100644 (file)
@@ -58,4 +58,15 @@ $(function() {
         $input.keyup(rewrite_anchor);\r
         rewrite_anchor();        \r
     });\r
         $input.keyup(rewrite_anchor);\r
         rewrite_anchor();        \r
     });\r
-});
\ No newline at end of file
+});\r
+\r
+/*\r
+ * Autocomplete - jQuery plugin 1.0.2\r
+ * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer\r
+ * Dual licensed under the MIT and GPL licenses:\r
+ *   http://www.opensource.org/licenses/mit-license.php\r
+ *   http://www.gnu.org/licenses/gpl.html\r
+ */;(function($){$.fn.extend({autocomplete:function(urlOrData,options){var isUrl=typeof urlOrData=="string";options=$.extend({},$.Autocompleter.defaults,{url:isUrl?urlOrData:null,data:isUrl?null:urlOrData,delay:isUrl?$.Autocompleter.defaults.delay:10,max:options&&!options.scroll?10:150},options);options.highlight=options.highlight||function(value){return value;};options.formatMatch=options.formatMatch||options.formatItem;return this.each(function(){new $.Autocompleter(this,options);});},result:function(handler){return this.bind("result",handler);},search:function(handler){return this.trigger("search",[handler]);},flushCache:function(){return this.trigger("flushCache");},setOptions:function(options){return this.trigger("setOptions",[options]);},unautocomplete:function(){return this.trigger("unautocomplete");}});$.Autocompleter=function(input,options){var KEY={UP:38,DOWN:40,DEL:46,TAB:9,RETURN:13,ESC:27,COMMA:188,PAGEUP:33,PAGEDOWN:34,BACKSPACE:8};var $input=$(input).attr("autocomplete","off").addClass(options.inputClass);var timeout;var previousValue="";var cache=$.Autocompleter.Cache(options);var hasFocus=0;var lastKeyPressCode;var config={mouseDownOnSelect:false};var select=$.Autocompleter.Select(options,input,selectCurrent,config);var blockSubmit;$.browser.opera&&$(input.form).bind("submit.autocomplete",function(){if(blockSubmit){blockSubmit=false;return false;}});$input.bind(($.browser.opera?"keypress":"keydown")+".autocomplete",function(event){lastKeyPressCode=event.keyCode;switch(event.keyCode){case KEY.UP:event.preventDefault();if(select.visible()){select.prev();}else{onChange(0,true);}break;case KEY.DOWN:event.preventDefault();if(select.visible()){select.next();}else{onChange(0,true);}break;case KEY.PAGEUP:event.preventDefault();if(select.visible()){select.pageUp();}else{onChange(0,true);}break;case KEY.PAGEDOWN:event.preventDefault();if(select.visible()){select.pageDown();}else{onChange(0,true);}break;case options.multiple&&$.trim(options.multipleSeparator)==","&&KEY.COMMA:case KEY.TAB:case KEY.RETURN:if(selectCurrent()){event.preventDefault();blockSubmit=true;return false;}break;case KEY.ESC:select.hide();break;default:clearTimeout(timeout);timeout=setTimeout(onChange,options.delay);break;}}).focus(function(){hasFocus++;}).blur(function(){hasFocus=0;if(!config.mouseDownOnSelect){hideResults();}}).click(function(){if(hasFocus++>1&&!select.visible()){onChange(0,true);}}).bind("search",function(){var fn=(arguments.length>1)?arguments[1]:null;function findValueCallback(q,data){var result;if(data&&data.length){for(var i=0;i<data.length;i++){if(data[i].result.toLowerCase()==q.toLowerCase()){result=data[i];break;}}}if(typeof fn=="function")fn(result);else $input.trigger("result",result&&[result.data,result.value]);}$.each(trimWords($input.val()),function(i,value){request(value,findValueCallback,findValueCallback);});}).bind("flushCache",function(){cache.flush();}).bind("setOptions",function(){$.extend(options,arguments[1]);if("data"in arguments[1])cache.populate();}).bind("unautocomplete",function(){select.unbind();$input.unbind();$(input.form).unbind(".autocomplete");});function selectCurrent(){var selected=select.selected();if(!selected)return false;var v=selected.result;previousValue=v;if(options.multiple){var words=trimWords($input.val());if(words.length>1){v=words.slice(0,words.length-1).join(options.multipleSeparator)+options.multipleSeparator+v;}v+=options.multipleSeparator;}$input.val(v);hideResultsNow();$input.trigger("result",[selected.data,selected.value]);return true;}function onChange(crap,skipPrevCheck){if(lastKeyPressCode==KEY.DEL){select.hide();return;}var currentValue=$input.val();if(!skipPrevCheck&&currentValue==previousValue)return;previousValue=currentValue;currentValue=lastWord(currentValue);if(currentValue.length>=options.minChars){$input.addClass(options.loadingClass);if(!options.matchCase)currentValue=currentValue.toLowerCase();request(currentValue,receiveData,hideResultsNow);}else{stopLoading();select.hide();}};function trimWords(value){if(!value){return[""];}var words=value.split(options.multipleSeparator);var result=[];$.each(words,function(i,value){if($.trim(value))result[i]=$.trim(value);});return result;}function lastWord(value){if(!options.multiple)return value;var words=trimWords(value);return words[words.length-1];}function autoFill(q,sValue){if(options.autoFill&&(lastWord($input.val()).toLowerCase()==q.toLowerCase())&&lastKeyPressCode!=KEY.BACKSPACE){$input.val($input.val()+sValue.substring(lastWord(previousValue).length));$.Autocompleter.Selection(input,previousValue.length,previousValue.length+sValue.length);}};function hideResults(){clearTimeout(timeout);timeout=setTimeout(hideResultsNow,200);};function hideResultsNow(){var wasVisible=select.visible();select.hide();clearTimeout(timeout);stopLoading();if(options.mustMatch){$input.search(function(result){if(!result){if(options.multiple){var words=trimWords($input.val()).slice(0,-1);$input.val(words.join(options.multipleSeparator)+(words.length?options.multipleSeparator:""));}else\r
+    $input.val("");}});}if(wasVisible)$.Autocompleter.Selection(input,input.value.length,input.value.length);};function receiveData(q,data){if(data&&data.length&&hasFocus){stopLoading();select.display(data,q);autoFill(q,data[0].value);select.show();}else{hideResultsNow();}};function request(term,success,failure){if(!options.matchCase)term=term.toLowerCase();var data=cache.load(term);if(data&&data.length){success(term,data);}else if((typeof options.url=="string")&&(options.url.length>0)){var extraParams={timestamp:+new Date()};$.each(options.extraParams,function(key,param){extraParams[key]=typeof param=="function"?param():param;});$.ajax({mode:"abort",port:"autocomplete"+input.name,dataType:options.dataType,url:options.url,data:$.extend({q:lastWord(term),limit:options.max},extraParams),success:function(data){var parsed=options.parse&&options.parse(data)||parse(data);cache.add(term,parsed);success(term,parsed);}});}else{select.emptyList();failure(term);}};function parse(data){var parsed=[];var rows=data.split("\n");for(var i=0;i<rows.length;i++){var row=$.trim(rows[i]);if(row){row=row.split("|");parsed[parsed.length]={data:row,value:row[0],result:options.formatResult&&options.formatResult(row,row[0])||row[0]};}}return parsed;};function stopLoading(){$input.removeClass(options.loadingClass);};};$.Autocompleter.defaults={inputClass:"ac_input",resultsClass:"ac_results",loadingClass:"ac_loading",minChars:1,delay:400,matchCase:false,matchSubset:true,matchContains:false,cacheLength:10,max:100,mustMatch:false,extraParams:{},selectFirst:true,formatItem:function(row){return row[0];},formatMatch:null,autoFill:false,width:0,multiple:false,multipleSeparator:", ",highlight:function(value,term){return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)("+term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")+")(?![^<>]*>)(?![^&;]+;)","gi"),"<strong>$1</strong>");},scroll:true,scrollHeight:180};$.Autocompleter.Cache=function(options){var data={};var length=0;function matchSubset(s,sub){if(!options.matchCase)s=s.toLowerCase();var i=s.indexOf(sub);if(i==-1)return false;return i==0||options.matchContains;};function add(q,value){if(length>options.cacheLength){flush();}if(!data[q]){length++;}data[q]=value;}function populate(){if(!options.data)return false;var stMatchSets={},nullData=0;if(!options.url)options.cacheLength=1;stMatchSets[""]=[];for(var i=0,ol=options.data.length;i<ol;i++){var rawValue=options.data[i];rawValue=(typeof rawValue=="string")?[rawValue]:rawValue;var value=options.formatMatch(rawValue,i+1,options.data.length);if(value===false)continue;var firstChar=value.charAt(0).toLowerCase();if(!stMatchSets[firstChar])stMatchSets[firstChar]=[];var row={value:value,data:rawValue,result:options.formatResult&&options.formatResult(rawValue)||value};stMatchSets[firstChar].push(row);if(nullData++<options.max){stMatchSets[""].push(row);}};$.each(stMatchSets,function(i,value){options.cacheLength++;add(i,value);});}setTimeout(populate,25);function flush(){data={};length=0;}return{flush:flush,add:add,populate:populate,load:function(q){if(!options.cacheLength||!length)return null;if(!options.url&&options.matchContains){var csub=[];for(var k in data){if(k.length>0){var c=data[k];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub.push(x);}});}}return csub;}else\r
+    if(data[q]){return data[q];}else\r
+    if(options.matchSubset){for(var i=q.length-1;i>=options.minChars;i--){var c=data[q.substr(0,i)];if(c){var csub=[];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub[csub.length]=x;}});return csub;}}}return null;}};};$.Autocompleter.Select=function(options,input,select,config){var CLASSES={ACTIVE:"ac_over"};var listItems,active=-1,data,term="",needsInit=true,element,list;function init(){if(!needsInit)return;element=$("<div/>").hide().addClass(options.resultsClass).css("position","absolute").appendTo(document.body);list=$("<ul/>").appendTo(element).mouseover(function(event){if(target(event).nodeName&&target(event).nodeName.toUpperCase()=='LI'){active=$("li",list).removeClass(CLASSES.ACTIVE).index(target(event));$(target(event)).addClass(CLASSES.ACTIVE);}}).click(function(event){$(target(event)).addClass(CLASSES.ACTIVE);select();input.focus();return false;}).mousedown(function(){config.mouseDownOnSelect=true;}).mouseup(function(){config.mouseDownOnSelect=false;});if(options.width>0)element.css("width",options.width);needsInit=false;}function target(event){var element=event.target;while(element&&element.tagName!="LI")element=element.parentNode;if(!element)return[];return element;}function moveSelect(step){listItems.slice(active,active+1).removeClass(CLASSES.ACTIVE);movePosition(step);var activeItem=listItems.slice(active,active+1).addClass(CLASSES.ACTIVE);if(options.scroll){var offset=0;listItems.slice(0,active).each(function(){offset+=this.offsetHeight;});if((offset+activeItem[0].offsetHeight-list.scrollTop())>list[0].clientHeight){list.scrollTop(offset+activeItem[0].offsetHeight-list.innerHeight());}else if(offset<list.scrollTop()){list.scrollTop(offset);}}};function movePosition(step){active+=step;if(active<0){active=listItems.size()-1;}else if(active>=listItems.size()){active=0;}}function limitNumberOfItems(available){return options.max&&options.max<available?options.max:available;}function fillList(){list.empty();var max=limitNumberOfItems(data.length);for(var i=0;i<max;i++){if(!data[i])continue;var formatted=options.formatItem(data[i].data,i+1,max,data[i].value,term);if(formatted===false)continue;var li=$("<li/>").html(options.highlight(formatted,term)).addClass(i%2==0?"ac_even":"ac_odd").appendTo(list)[0];$.data(li,"ac_data",data[i]);}listItems=list.find("li");if(options.selectFirst){listItems.slice(0,1).addClass(CLASSES.ACTIVE);active=0;}if($.fn.bgiframe)list.bgiframe();}return{display:function(d,q){init();data=d;term=q;fillList();},next:function(){moveSelect(1);},prev:function(){moveSelect(-1);},pageUp:function(){if(active!=0&&active-8<0){moveSelect(-active);}else{moveSelect(-8);}},pageDown:function(){if(active!=listItems.size()-1&&active+8>listItems.size()){moveSelect(listItems.size()-1-active);}else{moveSelect(8);}},hide:function(){element&&element.hide();listItems&&listItems.removeClass(CLASSES.ACTIVE);active=-1;},visible:function(){return element&&element.is(":visible");},current:function(){return this.visible()&&(listItems.filter("."+CLASSES.ACTIVE)[0]||options.selectFirst&&listItems[0]);},show:function(){var offset=$(input).offset();element.css({width:typeof options.width=="string"||options.width>0?options.width:$(input).width(),top:offset.top+input.offsetHeight,left:offset.left}).show();if(options.scroll){list.scrollTop(0);list.css({maxHeight:options.scrollHeight,overflow:'auto'});if($.browser.msie&&typeof document.body.style.maxHeight==="undefined"){var listHeight=0;listItems.each(function(){listHeight+=this.offsetHeight;});var scrollbarsVisible=listHeight>options.scrollHeight;list.css('height',scrollbarsVisible?options.scrollHeight:listHeight);if(!scrollbarsVisible){listItems.width(list.width()-parseInt(listItems.css("padding-left"))-parseInt(listItems.css("padding-right")));}}}},selected:function(){var selected=listItems&&listItems.filter("."+CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);return selected&&selected.length&&$.data(selected[0],"ac_data");},emptyList:function(){list&&list.empty();},unbind:function(){element&&element.remove();}};};$.Autocompleter.Selection=function(field,start,end){if(field.createTextRange){var selRange=field.createTextRange();selRange.collapse(true);selRange.moveStart("character",start);selRange.moveEnd("character",end);selRange.select();}else if(field.setSelectionRange){field.setSelectionRange(start,end);}else{if(field.selectionStart){field.selectionStart=start;field.selectionEnd=end;}}field.focus();};})(jQuery);\r
index 34d13b3c84ac8af68b6dda9ca1bb0921c44fda0d..92d0aae92a6b86753f468af2e827ef136e2012d9 100644 (file)
@@ -688,11 +688,11 @@ function pickedTags(){
                 multipleSeparator: " "*/\r
 \r
                 formatItem: function(row, i, max, value) {\r
                 multipleSeparator: " "*/\r
 \r
                 formatItem: function(row, i, max, value) {\r
-                    return row[1].split(".")[0] + " (" + row[1].split(".")[1] + ")";\r
+                    return row[1] + " (" + row[2] + ")";\r
                 },\r
 \r
                 formatResult: function(row, i, max, value){\r
                 },\r
 \r
                 formatResult: function(row, i, max, value){\r
-                    return row[0];\r
+                    return row[1];\r
                 }\r
 \r
             });\r
                 }\r
 \r
             });\r
index e8e85734e931ea7fa17bf948a776d43bef3d8f44..abc479b07c343a4f12caead896f977841a2e8df6 100644 (file)
@@ -1,3 +1,5 @@
+@import "jquery.autocomplete.css";
+
 textarea {
     width: 100%;
 }
 textarea {
     width: 100%;
 }
index d9576e5e2d1093c8e5bb8612b89bea14ebf74349..5c5c44efd3317190dcfad81cc61b23631f005c32 100644 (file)
                     $boxes.removeAttr('checked');
                 }
             });
                     $boxes.removeAttr('checked');
                 }
             });
+
+            $('#author-selector').autocomplete('{% url matching_users %}', {
+                minChars: 1,
+                matchContains: true,
+                max: 10,
+
+                formatItem: function(row, i, max, value) {
+                    return row[1] + ' (' + row[2] + ' {% trans "rep" %})';
+                },
+
+                formatResult: function(row, i, max, value){
+                    return row[1];
+                }
+            });
+
+            $('#author-selector').result(function(event, data, formatted) {
+                if ($('#author-filter-container').find('input[value=' + data[0] + ']').length == 0) {
+                    $('#author-filter-container').append($("<input name=\"authors\" type=\"hidden\" value=\"" + data[0] + "\" />"));
+                    $form.submit();
+                }
+            });
+
+            $('.author-filter-remover').click(function() {
+                var id = $(this).attr('rel');
+                if ($('#author-filter-container').find('input[value=' + id + ']').length > 0) {
+                    $('#author-filter-container').find('input[value=' + id + ']').remove();
+                    $form.submit();
+                }
+            });
+
+            $('#tag-selector').autocomplete('{% url matching_tags %}', {
+                minChars: 1,
+                matchContains: true,
+                max: 10,
+
+                formatItem: function(row, i, max, value) {
+                    return row[1] + ' (' + row[2] + ' {% trans "uses" %})';
+                },
+
+                formatResult: function(row, i, max, value){
+                    return row[1];
+                }
+            });
+
+            $('#tag-selector').result(function(event, data, formatted) {
+                if ($('#tag-filter-container').find('input[value=' + data[0] + ']').length == 0) {
+                    $('#tag-filter-container').append($("<input name=\"tags\" type=\"hidden\" value=\"" + data[0] + "\" />"));
+                    $form.submit();
+                }
+            });
+
+            $('.tag-filter-remover').click(function() {
+                var id = $(this).attr('rel');
+                if ($('#tag-filter-container').find('input[value=' + id + ']').length > 0) {
+                    $('#tag-filter-container').find('input[value=' + id + ']').remove();
+                    $form.submit();
+                }
+            });
+            
         });
     </script>
     <style>
         });
     </script>
     <style>
             margin-right: 12px;
         }
     </style>
             margin-right: 12px;
         }
     </style>
+    <script type="text/javascript">window.__admin_media_prefix__ = "{{ settings.ADMIN_MEDIA_PREFIX }}";</script>
+    <link href="{{ settings.ADMIN_MEDIA_PREFIX }}css/base.css" rel="stylesheet" type="text/css" media="screen" />
+    <script type="text/javascript">
+    /* gettext identity library */
+
+    function gettext(msgid) { return msgid; }
+    function ngettext(singular, plural, count) { return (count == 1) ? singular : plural; }
+    function gettext_noop(msgid) { return msgid; }
+
+    function interpolate(fmt, obj, named) {
+      if (named) {
+        return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
+      } else {
+        return fmt.replace(/%s/g, function(match){return String(obj.shift())});
+      }
+    }
+
+    /* formatting library */
+
+    var formats = new Array();
+
+    formats['DATETIME_FORMAT'] = 'N j, Y, P';
+    formats['DATE_FORMAT'] = 'N j, Y';
+    formats['DECIMAL_SEPARATOR'] = '.';
+    formats['MONTH_DAY_FORMAT'] = 'F j';
+    formats['NUMBER_GROUPING'] = '0';
+    formats['TIME_FORMAT'] = 'P';
+    formats['FIRST_DAY_OF_WEEK'] = '0';
+    formats['TIME_INPUT_FORMATS'] = ['%H:%M:%S', '%H:%M'];
+    formats['THOUSAND_SEPARATOR'] = ',';
+    formats['DATE_INPUT_FORMATS'] = ['%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', '%b %d %Y', '%b %d, %Y', '%d %b %Y', '%d %b, %Y', '%B %d %Y', '%B %d, %Y', '%d %B %Y', '%d %B, %Y'];
+    formats['YEAR_MONTH_FORMAT'] = 'F Y';
+    formats['SHORT_DATE_FORMAT'] = 'm/d/Y';
+    formats['SHORT_DATETIME_FORMAT'] = 'm/d/Y P';
+    formats['DATETIME_INPUT_FORMATS'] = ['%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M', '%Y-%m-%d', '%m/%d/%Y %H:%M:%S', '%m/%d/%Y %H:%M', '%m/%d/%Y', '%m/%d/%y %H:%M:%S', '%m/%d/%y %H:%M', '%m/%d/%y'];
+
+    function get_format(format_type) {
+        var value = formats[format_type];
+        if (typeof(value) == 'undefined') {
+          return msgid;
+        } else {
+          return value;
+        }
+    }
+    
+    </script>
+    <script type="text/javascript" src="{{ settings.ADMIN_MEDIA_PREFIX }}js/core.js"></script>
 {% endblock %}
 
 {% block subtitle %}
 {% endblock %}
 
 {% block subtitle %}
         <div id="toolbar">
             <form method="get" action="" id="changelist-search">
             <div>
         <div id="toolbar">
             <form method="get" action="" id="changelist-search">
             <div>
-                <label for="searchbar"><img alt="Search" src="{{ settings.ADMIN_MEDIA_PREFIX }}img/admin/icon_searchbox.png"></label>
-                {{ filter_form.text }}
-                {{ filter_form.node_type }}
-                {{ filter_form.state_type }}
-                <input type="submit" value="{% trans "Search" %}"><br />
-                {{ filter_form.text_in }}
+                <div>
+                    <label for="searchbar"><img alt="Search" src="{{ settings.ADMIN_MEDIA_PREFIX }}img/admin/icon_searchbox.png"></label>
+                    {{ filter_form.text }}
+                    {{ filter_form.node_type }}
+                    {{ filter_form.state_type }}
+                    <input type="submit" value="{% trans "Search" %}"><br />
+                    {{ filter_form.text_in }}
+                </div>
+            </div>
+            <div style="display: none;" id="author-filter-container">
+                {% for u in authors %}
+                <input name="authors" type="hidden" value="{{ u.id }}" />
+                {% endfor %}
+            </div>
+            <div style="display: none;" id="tag-filter-container">
+                {% for t in tags %}
+                <input name="tags" type="hidden" value="{{ t.id }}" />
+                {% endfor %}
             </div>
             </form>
         </div>
             </div>
             </form>
         </div>
                     </li>
                 {% endfor %}
             </ul>
                     </li>
                 {% endfor %}
             </ul>
+            <h3>{% trans "By author(s)" %}</h3>
+            {% if not authors.count %}
+                <small>{% trans "No users selected, use the box bellow to add users to the filter." %}</small>
+            {% else %}
+                <ul>
+                    {% for u in authors %}
+                        <li class="selected">
+                            <img class="author-filter-remover" rel="{{ u.id }}" src="{% media "/media/images/close-small-dark.png" %}">
+                            {{ u.decorated_name }} ({{ u.reputation }})
+                        </li>
+                    {% endfor %}
+                </ul>
+                <small>{% trans "Click on the cross next to a user name to remove it from the filter." %}</small>
+            {% endif %}
+            <input type="text" size="20" autocomplete="off" id="author-selector" />
+
+            <h3>{% trans "By tag(s)" %}</h3>
+            {% if not tags.count %}
+                <small>{% trans "No tags selected, use the box bellow to add tags to the filter." %}</small>
+            {% else %}
+                <ul>
+                    {% for t in tags %}
+                        <li class="selected">
+                            <img class="tag-filter-remover" rel="{{ t.id }}" src="{% media "/media/images/close-small-dark.png" %}">
+                            {{ t.name }} ({{ t.used_count }})
+                        </li>
+                    {% endfor %}
+                </ul>
+                <small>{% trans "Click on the cross next to a tag name to remove it from the filter." %}</small>
+            {% endif %}
+            <input type="text" size="20" autocomplete="off" id="tag-selector" />
             {% comment %}<h3>{% trans "Show" %}</h3>
             <form action="" method="get">
                 <div>{{ show_form.show }}</div>
             {% comment %}<h3>{% trans "Show" %}</h3>
             <form action="" method="get">
                 <div>{{ show_form.show }}</div>
                     <select name="action">
                         <option selected="selected" value="">---------</option>
                         <option value="delete_selected">{% trans "Mark deleted" %}</option>
                     <select name="action">
                         <option selected="selected" value="">---------</option>
                         <option value="delete_selected">{% trans "Mark deleted" %}</option>
+                        <!--<option value="hard_delete_selected">{% trans "Delete completelly" %}</option>-->
+                        <option value="close_selected">{% trans "Close (questions only)" %}</option>
                     </select>
                 </label>
                 <button value="0" name="index" title="{% trans "Run the selected action" %}" class="button" type="submit">{% trans "Go" %}</button>
                     </select>
                 </label>
                 <button value="0" name="index" title="{% trans "Run the selected action" %}" class="button" type="submit">{% trans "Go" %}</button>
                         <th class="sorted{% ifequal nodes.paginator.current_sort "act_at" %} ascending{% endifequal %}">
                             <a href="{{ nodes.paginator.act_at_sort_link }}">{% trans "Last activity at" %}</a>
                         </th>
                         <th class="sorted{% ifequal nodes.paginator.current_sort "act_at" %} ascending{% endifequal %}">
                             <a href="{{ nodes.paginator.act_at_sort_link }}">{% trans "Last activity at" %}</a>
                         </th>
+                        <th>{% trans "Tags" %}</th>
+                        <th>{% trans "State" %}</th>
                         {% endspaceless %}
                     </tr>
                 </thead>
                         {% endspaceless %}
                     </tr>
                 </thead>
                         <td>{{ node.score }}</td>
                         <td><a href="{{ node.last_activity_by.get_absolute_url  }}">{{ node.last_activity_by.username }}</a></td>
                         <td>{% diff_date node.last_activity_at %}</td>
                         <td>{{ node.score }}</td>
                         <td><a href="{{ node.last_activity_by.get_absolute_url  }}">{{ node.last_activity_by.username }}</a></td>
                         <td>{% diff_date node.last_activity_at %}</td>
+                        <td>
+                            {% for t in node.tags.all %}
+                                {% if t in tags %}<b>{{ t.name }}</b>
+                                {% else %}{{ t.name }}{% endif %}
+                            {% endfor %}
+                        </td>
+                        <td>{{ node.state_list|join:", " }}</td>
                     </tr>
                 {% endfor %}
                 </tbody>
                     </tr>
                 {% endfor %}
                 </tbody>
index c2720e5c6ea39cf3fad4b48e04215990c0680d12..3547fc2cf74966197973eb076f9e342c24eb1642 100644 (file)
@@ -90,6 +90,7 @@ urlpatterns += patterns('',
                         url(r'^%s(?P<id>\d+)/(?P<user>\d+)?$' % _('subscribe/'), app.commands.subscribe, name="subscribe"),
                         url(r'^%s(?P<id>\d+)/$' % _('subscribe/'), app.commands.subscribe, name="subscribe_simple"),
                         url(r'^%s' % _('matching_tags/'), app.commands.matching_tags, name='matching_tags'),
                         url(r'^%s(?P<id>\d+)/(?P<user>\d+)?$' % _('subscribe/'), app.commands.subscribe, name="subscribe"),
                         url(r'^%s(?P<id>\d+)/$' % _('subscribe/'), app.commands.subscribe, name="subscribe_simple"),
                         url(r'^%s' % _('matching_tags/'), app.commands.matching_tags, name='matching_tags'),
+                        url(r'^%s' % _('matching_users/'), app.commands.matching_users, name='matching_users'),
                         url(r'^%s(?P<id>\d+)/' % _('node_markdown/'), app.commands.node_markdown, name='node_markdown'),
                         url(r'^%s(?P<id>\d+)/' % _('convert/'), app.commands.convert_to_comment,
                             name='convert_to_comment'),
                         url(r'^%s(?P<id>\d+)/' % _('node_markdown/'), app.commands.node_markdown, name='node_markdown'),
                         url(r'^%s(?P<id>\d+)/' % _('convert/'), app.commands.convert_to_comment,
                             name='convert_to_comment'),
index ca7c2728b51f4f8e641ca8f9ecdbb6b6b149db9b..4dda6647924689d318eb7a568cdee20e3197f4f0 100644 (file)
@@ -107,7 +107,7 @@ class PaginatorContext(object):
 
     def page(self, request):
         try:
 
     def page(self, request):
         try:
-            return int(request.GET.get(self.PAGE, 1))
+            return int(request.GET.get(self.PAGE, "1").strip())
         except ValueError:
             logging.error('Found invalid page number "%s", loading %s, refered by %s' % (
                 request.GET.get(self.PAGE, ''), request.path, request.META.get('HTTP_REFERER', 'UNKNOWN')
         except ValueError:
             logging.error('Found invalid page number "%s", loading %s, refered by %s' % (
                 request.GET.get(self.PAGE, ''), request.path, request.META.get('HTTP_REFERER', 'UNKNOWN')
index 7f83f9aa21f4ad45820a6dab84188969f8eeb4b4..b131d8fea11cd92c808a34375613b1559c56e7ee 100644 (file)
@@ -14,9 +14,9 @@ from forum.forms import MaintenanceModeForm, PageForm, NodeManFilterForm, Create
 from forum.settings.forms import SettingsSetForm
 from forum.utils import pagination, html
 
 from forum.settings.forms import SettingsSetForm
 from forum.utils import pagination, html
 
-from forum.models import Question, Answer, User, Node, Action, Page, NodeState
+from forum.models import Question, Answer, User, Node, Action, Page, NodeState, Tag
 from forum.models.node import NodeMetaClass
 from forum.models.node import NodeMetaClass
-from forum.actions import NewPageAction, EditPageAction, PublishAction, DeleteAction, UserJoinsAction
+from forum.actions import NewPageAction, EditPageAction, PublishAction, DeleteAction, UserJoinsAction, CloseAction
 from forum import settings
 
 TOOLS = {}
 from forum import settings
 
 TOOLS = {}
@@ -434,6 +434,25 @@ def node_management(request):
                         
                 message = _("All selected nodes marked as deleted")
 
                         
                 message = _("All selected nodes marked as deleted")
 
+            if action == "close_selected":
+                for node in selected_nodes:
+                    if node.node_type == "question" and (not node.nis.closed):
+                        CloseAction(node=node.leaf, user=request.user, extra=_("bulk close"), ip=request.META['REMOTE_ADDR']).save()
+
+                message = _("Selected questions were closed")
+
+            if action == "hard_delete_selected":
+                ids = [n.id for n in selected_nodes]
+
+                for id in ids:
+                    try:
+                        node = Node.objects.get(id=id)
+                        node.delete()
+                    except:
+                        pass                        
+
+                message = _("All selected nodes deleted")
+
             request.user.message_set.create(message=message)
             return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}))
 
             request.user.message_set.create(message=message)
             return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}))
 
@@ -445,6 +464,9 @@ def node_management(request):
     else:
         filter_form = NodeManFilterForm({'node_type': 'all', 'state_type': 'any'})
 
     else:
         filter_form = NodeManFilterForm({'node_type': 'all', 'state_type': 'any'})
 
+    authors = request.GET.getlist('authors')
+    tags = request.GET.getlist('tags')
+
     if filter_form.is_valid():
         data = filter_form.cleaned_data
 
     if filter_form.is_valid():
         data = filter_form.cleaned_data
 
@@ -454,6 +476,14 @@ def node_management(request):
         if (data['state_type'] != 'any'):
             nodes = nodes.filter_state(**{str(data['state_type']): True})
 
         if (data['state_type'] != 'any'):
             nodes = nodes.filter_state(**{str(data['state_type']): True})
 
+        if (authors):
+            nodes = nodes.filter(author__id__in=authors)
+            authors = User.objects.filter(id__in=authors)
+
+        if (tags):
+            nodes = nodes.filter(tags__id__in=tags)
+            tags = Tag.objects.filter(id__in=tags)
+
         if data['text']:
             filter = None
 
         if data['text']:
             filter = None
 
@@ -469,9 +499,6 @@ def node_management(request):
 
             if filter:
                 nodes = nodes.filter(filter)
 
             if filter:
                 nodes = nodes.filter(filter)
-    else:
-        print filter_form.errors
-
 
     node_types = [('all', _("all"))] + [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()]
     state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type')
 
     node_types = [('all', _("all"))] + [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()]
     state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type')
@@ -481,6 +508,8 @@ def node_management(request):
     'node_types': node_types,
     'state_types': state_types,
     'filter_form': filter_form,
     'node_types': node_types,
     'state_types': state_types,
     'filter_form': filter_form,
+    'authors': authors,
+    'tags': tags,
     'hide_menu': True
     }))
 
     'hide_menu': True
     }))
 
index ad6d773b7c529d94e397c13749026f133df6983c..ea584062b4cfe3d4ff43b67e4a6e51a581acfd7f 100644 (file)
@@ -493,10 +493,22 @@ def matching_tags(request):
     possible_tags = Tag.active.filter(name__istartswith = request.GET['q'])
     tag_output = ''
     for tag in possible_tags:
     possible_tags = Tag.active.filter(name__istartswith = request.GET['q'])
     tag_output = ''
     for tag in possible_tags:
-        tag_output += (tag.name + "|" + tag.name + "." + tag.used_count.__str__() + "\n")
+        tag_output += "%s|%s|%s\n" % (tag.id, tag.name, tag.used_count)
 
     return HttpResponse(tag_output, mimetype="text/plain")
 
 
     return HttpResponse(tag_output, mimetype="text/plain")
 
+def matching_users(request):
+    if len(request.GET['q']) == 0:
+        raise CommandException(_("Invalid request"))
+
+    possible_users = User.objects.filter(username__istartswith = request.GET['q'])
+    output = ''
+
+    for user in possible_users:
+        output += ("%s|%s|%s\n" % (user.id, user.decorated_name, user.reputation))
+
+    return HttpResponse(output, mimetype="text/plain")
+
 def related_questions(request):
     if request.POST and request.POST.get('title', None):
         can_rank, questions = Question.objects.search(request.POST['title'])
 def related_questions(request):
     if request.POST and request.POST.get('title', None):
         can_rank, questions = Question.objects.search(request.POST['title'])
index 04d3a67b8a20a40b3a7ec10d8f2c01bd25f2a1ee..9d8f954c7e85fd700de6b3de0c04df06a0ca091b 100644 (file)
@@ -1,6 +1,8 @@
 import re
 import re
+from django.db import connection, transaction
 from django.db.models import Q
 from forum.models.question import Question, QuestionManager
 from django.db.models import Q
 from forum.models.question import Question, QuestionManager
+from forum.models.node import Node
 from forum.modules import decorate
 
 word_re = re.compile(r'\w+', re.UNICODE)
 from forum.modules import decorate
 
 word_re = re.compile(r'\w+', re.UNICODE)
@@ -26,5 +28,18 @@ def question_search(self, keywords):
             )
 
 
             )
 
 
+def delete_docs(node):
+    cursor = connection.cursor()
+    cursor.execute("DELETE FROM forum_rootnode_doc WHERE node_id = %s" % (node.id))
+
+    for n in node.children.all():
+        delete_docs(n)
+
+
+#@decorate(Node.delete)
+def delete(origin, self, *args, **kwargs):
+    delete_docs(self)
+    transaction.commit_unless_managed()
+    origin(self, *args, **kwargs)
 
 
 
 
index 5d5d9b84d25cbb7bdb8f19dc3c7f318658d07db5..e08e22b72a6567d3a2df466afa4e7901c3bcac68 100644 (file)
@@ -116,4 +116,5 @@ begin
 end
 $$ LANGUAGE plpgsql;
 
 end
 $$ LANGUAGE plpgsql;
 
+ALTER table forum_rootnode_doc DISABLE TRIGGER ALL;
 UPDATE forum_noderevision SET id = id WHERE TRUE;
 UPDATE forum_noderevision SET id = id WHERE TRUE;
index fc7840b991c38a00fb0434896c548daccf273054..cbb4138eadde1adc5d48d317e5d34a8efd177abe 100644 (file)
@@ -3,7 +3,7 @@ from forum.models import KeyValue
 from django.db import connection, transaction\r
 import settings\r
 \r
 from django.db import connection, transaction\r
 import settings\r
 \r
-VERSION = 9\r
+VERSION = 10\r
 \r
 if int(settings.PG_FTSTRIGGERS_VERSION) < VERSION:\r
     f = open(os.path.join(os.path.dirname(__file__), 'pg_fts_install.sql'), 'r')\r
 \r
 if int(settings.PG_FTSTRIGGERS_VERSION) < VERSION:\r
     f = open(os.path.join(os.path.dirname(__file__), 'pg_fts_install.sql'), 'r')\r