]> git.openstreetmap.org Git - osqa.git/commitdiff
Bulk management changes:
authorhernani <hernani@0cfe37f9-358a-4d5e-be75-b63607b5c754>
Sat, 11 Sep 2010 00:59:10 +0000 (00:59 +0000)
committerhernani <hernani@0cfe37f9-358a-4d5e-be75-b63607b5c754>
Sat, 11 Sep 2010 00:59:10 +0000 (00:59 +0000)
Highlights nodes with deleted or accepted state.
Adds date and by to each state in the states column.
Shows the diamonds next to the usernames (su or staff)
Allows resizing the summary column via javascript (not perfect though).
Allows the colapsing of the filter panel.
Allows filtering by multiple nodes or state types.

git-svn-id: http://svn.osqa.net/svnroot/osqa/trunk@589 0cfe37f9-358a-4d5e-be75-b63607b5c754

forum/forms/admin.py
forum/models/node.py
forum/skins/default/templates/osqaadmin/nodeman.html
forum/views/admin.py

index 32d710bebc0a1f6d1f43a896a15c2065b9ae712c..a1d7b72d6be72d53eee14ccbc143894739b6a6de 100644 (file)
@@ -92,19 +92,6 @@ class PageForm(forms.Form):
 
     comments = forms.BooleanField(label=_("Allow comments"), initial=False, required=False)
 
 
     comments = forms.BooleanField(label=_("Allow comments"), initial=False, required=False)
 
-TEXT_IN_CHOICES = (
-('title', _('Title')),
-('body', _('Body')),
-('both', _('Title and Body'))
-)
-
-class NodeManFilterForm(forms.Form):
-    node_type = forms.CharField(widget=forms.HiddenInput, initial='all')
-    state_type = forms.CharField(widget=forms.HiddenInput, initial='any')
-    text = forms.CharField(required=False, widget=forms.TextInput(attrs={'size': 40}))
-    text_in = forms.ChoiceField(required=False, widget=forms.RadioSelect, choices=TEXT_IN_CHOICES, initial='title')
-
-
 from forum.forms.auth import SimpleRegistrationForm
 from forum.forms.general import SetPasswordForm
 
 from forum.forms.auth import SimpleRegistrationForm
 from forum.forms.general import SetPasswordForm
 
index ea83e7f2a24afd27bda25122e4697e8e85ec9797..f62a8e078b9c2fc17b8a80d6a39cc9617df6c9ff 100644 (file)
@@ -108,6 +108,30 @@ class NodeQuerySet(CachedQuerySet):
 
         return node
 
 
         return node
 
+    def any_state(self, *args):
+        filter = None
+
+        for s in args:
+            s_filter = models.Q(state_string__contains="(%s)" % s)
+            filter = filter and (filter | s_filter) or s_filter
+
+        if filter:
+            return self.filter(filter)
+        else:
+            return self
+
+    def all_states(self, *args):
+        filter = None
+
+        for s in args:
+            s_filter = models.Q(state_string__contains="(%s)" % s)
+            filter = filter and (filter & s_filter) or s_filter
+
+        if filter:
+            return self.filter(filter)
+        else:
+            return self
+
     def filter_state(self, **kwargs):
         apply_bool = lambda q, b: b and q or ~q
         return self.filter(*[apply_bool(models.Q(state_string__contains="(%s)" % s), b) for s, b in kwargs.items()])
     def filter_state(self, **kwargs):
         apply_bool = lambda q, b: b and q or ~q
         return self.filter(*[apply_bool(models.Q(state_string__contains="(%s)" % s), b) for s, b in kwargs.items()])
index af8d1ce37d7038824a4d3a14b4cce05232694120..500e05bf2f883d5186d598263653f7cc3162722b 100644 (file)
@@ -7,14 +7,37 @@
         $(function() {
             var $form = $('#changelist-search');
 
         $(function() {
             var $form = $('#changelist-search');
 
+            $('#all-node-type-link').click(function() {
+                $('#type-filter-container').find('input').remove();
+                $form.submit();
+            });
+
             $('.node-type-link').click(function() {
             $('.node-type-link').click(function() {
-                $form.find('#id_state_type').val('any');
-                $form.find('#id_node_type').val($(this).attr('href').substring(1));
+                var link_type = $(this).attr('href').substring(1);
+
+                if ($('#type-filter-container').find('input[value=' + link_type + ']').length == 0) {
+                    $('#type-filter-container').append($("<input name=\"node_type\" type=\"hidden\" value=\"" + link_type + "\" />"));
+                } else {
+                    $('#type-filter-container').find('input[value=' + link_type + ']').remove();
+                }
+
+                $form.submit();                
+            });
+
+            $('#all-state-link').click(function() {
+                $('#state-filter-container').find('input').remove();
                 $form.submit();
             });
 
             $('.state-type-link').click(function() {
                 $form.submit();
             });
 
             $('.state-type-link').click(function() {
-                $form.find('#id_state_type').val($(this).attr('href').substring(1));
+                var state_type = $(this).attr('href').substring(1);
+
+                if ($('#state-filter-container').find('input[value=' + state_type + ']').length == 0) {
+                    $('#state-filter-container').append($("<input name=\"state_type\" type=\"hidden\" value=\"" + state_type + "\" />"));
+                } else {
+                    $('#state-filter-container').find('input[value=' + state_type + ']').remove();
+                }
+
                 $form.submit();
             });
 
                 $form.submit();
             });
 
                 }
             });
 
                 }
             });
 
+            var resize_data = null;
+
+            $('.col-resizer').mousedown(function(e) {
+                var $to_resize = $(this).prev();
+
+                resize_data = {
+                    resizer: $(this),
+                    to_resize: $to_resize,
+                    start_width: $to_resize.innerWidth(),
+                    x_start: e.pageX,
+                }
+            });
+
+            $('body').mousemove(function(e) {
+                if (resize_data != null) {
+                    var new_size = (resize_data.start_width - (resize_data.x_start - e.pageX)) + 'px';
+                    resize_data.to_resize.css({'max-width': new_size, 'min-width': new_size})
+                    resize_data.resizer.css('max-width', '3px');
+                }
+            });
+
+            $('body').mouseup(function() {
+                if (resize_data != null)
+                    resize_data = null;
+            });
+
+            $('#filter-panel-header').click(function() {
+                $('#filter-panel').slideToggle();
+            });
+
+            $('#state-filter-type').change(function() {
+                $('#state-filter-type-hidden').val($(this).val());
+                $form.submit();
+            });
+            
+            $('#reset-text-filter').click(function() {
+                $('#text-filter-input').val('');
+                $form.submit();
+                return false;
+            });
         });
     </script>
     <style>
         });
     </script>
     <style>
             display: inline;
             margin-right: 12px;
         }
             display: inline;
             margin-right: 12px;
         }
+
+        #result_list tr.row1 td.deleted {
+            background-color: #FDD;
+        }
+
+        #result_list tr.row2 td.deleted {
+            background-color: #FEE;
+        }
+
+        #result_list tr.row1 td.accepted {
+            background-color: #DFD;
+        }
+
+        #result_list tr.row2 td.accepted {
+            background-color: #EFD;
+        }
+
+        span.question-deleted {
+            text-decoration: line-through;
+        }
+
+        .col-resizer {
+            width: 2px;
+            min-width: 2px;
+            min-width: 2px;
+            cursor: col-resize;
+            padding: 0 0 0 0;
+        }
     </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" />
     </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" />
             <form method="get" action="" id="changelist-search">
             <div>
                 <div>
             <form method="get" action="" id="changelist-search">
             <div>
                 <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 }}
+                    <label><img alt="Search" src="{{ settings.ADMIN_MEDIA_PREFIX }}img/admin/icon_searchbox.png"></label>
+                    <input type="text" size="40" name="text" id="text-filter-input" value="{{ text }}">
+                    <input type="submit" value="{% trans "Search" %}">
+                    {% if text %}
+                        <small><a href="#" id="reset-text-filter">{% trans "reset text filter" %}</a></small>
+                    {% endif %}
+                    <br />
+                    <ul>
+                        <li>
+                            <label>
+                                <input type="radio" name="text_in" value="title"{% ifequal text_in "title" %} checked="checked"{% endifequal %} />
+                            {% trans "Title" %}</label>
+                        </li>
+                        <li>
+                            <label>
+                                <input type="radio" name="text_in" value="body"{% ifequal text_in "body" %} checked="checked"{% endifequal %} />
+                            {% trans "Body" %}</label>
+                        </li>
+                        <li>
+                            <label>
+                                <input type="radio" name="text_in" value="both"{% ifequal text_in "both" %} checked="checked"{% endifequal %} />
+                            {% trans "Title and Body" %}</label>
+                       </li>
+                    </ul>
                 </div>
             </div>
             <input type="hidden" name="sort" value="{{ nodes.paginator.current_sort }}" />
                 </div>
             </div>
             <input type="hidden" name="sort" value="{{ nodes.paginator.current_sort }}" />
+            <input type="hidden" id="state-filter-type-hidden" name="state_filter_type" value="" />
             <div style="display: none;" id="author-filter-container">
                 {% for u in authors %}
                 <input name="authors" type="hidden" value="{{ u.id }}" />
             <div style="display: none;" id="author-filter-container">
                 {% for u in authors %}
                 <input name="authors" type="hidden" value="{{ u.id }}" />
                 <input name="tags" type="hidden" value="{{ t.id }}" />
                 {% endfor %}
             </div>
                 <input name="tags" type="hidden" value="{{ t.id }}" />
                 {% endfor %}
             </div>
+            <div id="type-filter-container" style="display: none;">
+                {% for type in type_filter %}
+                <input name="node_type" type="hidden" value="{{ type }}" />
+                {% endfor %}
+            </div>
+            <div id="state-filter-container" style="display: none;">
+                {% for type in state_filter %}
+                <input name="state_type" type="hidden" value="{{ type }}" />
+                {% endfor %}
+            </div>
             </form>
         </div>
         <div id="changelist-filter">
             </form>
         </div>
         <div id="changelist-filter">
-            <h2>{% trans "Filter" %}</h2>
+            <h2 id="filter-panel-header">{% trans "Filter" %}<small> ({% trans "Click to show/hide" %})</small></h2>
+            <div id="filter-panel">
             <h3>{% trans "By type" %}</h3>
             <ul>
             <h3>{% trans "By type" %}</h3>
             <ul>
+                <li {% if not type_filter %} class="selected"{% endif %}>
+                    <a id="all-node-type-link" href="#all" title="{% trans "click to clear the type filter" %}">{% trans "all" %}</a>
+                </li>
                 {% for type, name in node_types %}
                 {% for type, name in node_types %}
-                <li{% ifequal filter_form.node_type.data type %} class="selected"{% endifequal %}>
+                <li{% if type in type_filter %} class="selected" title="{% trans "click to remove from the filter" %}"{% else %} title="{% trans "click to add to the filter" %}"{% endif %}>
                     <a class="node-type-link" href="#{{ type }}">{{ name }}</a>
                 </li>
                 {% endfor %}
             </ul>
             <h3>{% trans "By state" %}</h3>
             <ul>
                     <a class="node-type-link" href="#{{ type }}">{{ name }}</a>
                 </li>
                 {% endfor %}
             </ul>
             <h3>{% trans "By state" %}</h3>
             <ul>
-                <li{% ifequal filter_form.state_type.data "any" %} class="selected"{% endifequal %}><a class="state-type-link" href="#any">{% trans "any" %}</a></li>
+                <li {% if not state_filter %} class="selected"{% endif %}>
+                    <a id="all-state-link" href="#any" title="{% trans "click to clear the state filter" %}">{% trans "any" %}</a>
+                </li>
                 {% for state_type in state_types %}
                 {% for state_type in state_types %}
-                    <li{% ifequal filter_form.state_type.data state_type %} class="selected"{% endifequal %}>
+                    <li{% if state_type in state_filter %} class="selected" title="{% trans "click to remove from the filter" %}"{% else %} title="{% trans "click to add to the filter" %}"{% endif %}>
                         <a class="state-type-link" href="#{{ state_type }}">{{ state_type }}</a>
                     </li>
                 {% endfor %}
                         <a class="state-type-link" href="#{{ state_type }}">{{ state_type }}</a>
                     </li>
                 {% endfor %}
+                <li>
+                    <select id="state-filter-type">
+                        <option value="any"{% ifequal state_filter_type "any" %} selected="selected"{% endifequal %}>{% trans "Match any selected" %}</option>
+                        <option value="all"{% ifequal state_filter_type "all" %} selected="selected"{% endifequal %}>{% trans "Match all selected" %}</option>
+                    </select>
+                </li>
             </ul>
             <h3>{% trans "By author(s)" %}</h3>
             {% if not authors.count %}
             </ul>
             <h3>{% trans "By author(s)" %}</h3>
             {% if not authors.count %}
                 <div>{{ show_form.show }}</div>
                 <input type="submit" value="{% trans "Refresh" %}" />
             </form>{% endcomment %}
                 <div>{{ show_form.show }}</div>
                 <input type="submit" value="{% trans "Refresh" %}" />
             </form>{% endcomment %}
+            </div>
         </div>
         <form id="changelist-form" method="POST" action="">
             <div class="actions">
         </div>
         <form id="changelist-form" method="POST" action="">
             <div class="actions">
                     <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="undelete_selected">{% trans "Undelete" %}</option>
                         <option value="hard_delete_selected">{% trans "Delete completelly" %}</option>
                         <option value="close_selected">{% trans "Close (questions only)" %}</option>
                     </select>
                         <option value="hard_delete_selected">{% trans "Delete completelly" %}</option>
                         <option value="close_selected">{% trans "Close (questions only)" %}</option>
                     </select>
                     <tr>
                         {% declare %}
                             current_sort = nodes.paginator.current_sort
                     <tr>
                         {% declare %}
                             current_sort = nodes.paginator.current_sort
-                            added_at = current_sort == "added_at" and "descending" or (current_sort == "added_at_asc" and "ascending" or "")
-                            score = current_sort == "score" and "descending" or (current_sort == "score_asc" and "ascending" or "")
-                            act_at = current_sort == "act_at" and "descending" or (current_sort == "act_at_asc" and "ascending" or "")
+                            added_at = current_sort == "added_at" and "ascending" or (current_sort == "added_at_asc" and "descending" or "")
+                            author = current_sort == "author" and "ascending" or (current_sort == "author_asc" and "descending" or "")
+                            score = current_sort == "score" and "ascending" or (current_sort == "score_asc" and "descending" or "")
+                            act_at = current_sort == "act_at" and "ascending" or (current_sort == "act_at_asc" and "descending" or "")
+                            act_by = current_sort == "act_by" and "ascending" or (current_sort == "act_by_asc" and "descending" or "")
 
                             added_at_link = current_sort == "added_at" and nodes.paginator.added_at_asc_sort_link or nodes.paginator.added_at_sort_link
 
                             added_at_link = current_sort == "added_at" and nodes.paginator.added_at_asc_sort_link or nodes.paginator.added_at_sort_link
+                            author_link = current_sort == "author_asc" and nodes.paginator.author_sort_link or nodes.paginator.author_asc_sort_link
                             score_link = current_sort == "score" and nodes.paginator.score_asc_sort_link or nodes.paginator.score_sort_link
                             act_at_link = current_sort == "act_at" and nodes.paginator.act_at_asc_sort_link or nodes.paginator.act_at_sort_link
                             score_link = current_sort == "score" and nodes.paginator.score_asc_sort_link or nodes.paginator.score_sort_link
                             act_at_link = current_sort == "act_at" and nodes.paginator.act_at_asc_sort_link or nodes.paginator.act_at_sort_link
+                            act_by_link = current_sort == "act_by_asc" and nodes.paginator.act_by_sort_link or nodes.paginator.act_by_asc_sort_link
                         {% enddeclare %}
                         {% spaceless %}
                         <th class="action-checkbox-column">
                             <input type="checkbox" id="action-toggle" style="display: inline;" />
                         {% enddeclare %}
                         {% spaceless %}
                         <th class="action-checkbox-column">
                             <input type="checkbox" id="action-toggle" style="display: inline;" />
-                        </th>
-                        {% ifequal filter_form.node_type.data "all" %}
-                            <th>{% trans "Type" %}</th>
-                        {% endifequal %}
+                        </th>                        
+                        <th>{% trans "Type" %}</th>
                         <th>{% trans "Summary" %}</th>
                         <th>{% trans "Summary" %}</th>
+                        <th class="col-resizer"></th>
                         <th>{% trans "State" %}</th>
                         <th>{% trans "State" %}</th>
-                        <th>{% trans "Author" %}</th>
+                        <th class="sorted {{ author }}">
+                            <a href="{{ author_link }}">{% trans "Author" %}</a>
+                        </th>
                         <th class="sorted {{ added_at }}">
                             <a href="{{ added_at_link }}">{% trans "Added at" %}</a>
                         </th>
                         <th class="sorted {{ added_at }}">
                             <a href="{{ added_at_link }}">{% trans "Added at" %}</a>
                         </th>
-                        <th class="sorted {{ score }}">
+                        <!--<th class="sorted {{ score }}">
                             <a href="{{ score_link }}">{% trans "Score" %}</a>
                             <a href="{{ score_link }}">{% trans "Score" %}</a>
+                        </th>-->
+                        <th class="sorted {{ act_by }}">
+                            <a href="{{ act_by_link }}">{% trans "Last activity by" %}</a>
                         </th>
                         </th>
-                        <th>{% trans "Last acivity by" %}</th>
                         <th class="sorted {{ act_at }}">
                             <a href="{{ act_at_link }}">{% trans "Last activity at" %}</a>
                         </th>
                         <th class="sorted {{ act_at }}">
                             <a href="{{ act_at_link }}">{% trans "Last activity at" %}</a>
                         </th>
                     </tr>
                 </thead>
                 <tbody>
                     </tr>
                 </thead>
                 <tbody>
+                {% with filter_form.state_type.data as state_type %}
                 {% for node in nodes.paginator.page %}
                     <tr class="{% cycle 'row1' 'row2' %}">
                         <td><input type="checkbox" name="_selected_node" value="{{ node.id }}" class="action-select"></td>
                 {% for node in nodes.paginator.page %}
                     <tr class="{% cycle 'row1' 'row2' %}">
                         <td><input type="checkbox" name="_selected_node" value="{{ node.id }}" class="action-select"></td>
-                        {% ifequal filter_form.node_type.data "all" %}
-                            <th>{{ node.friendly_name }}</th>
-                        {% endifequal %}
-                        <td>
-                            <a href="{{ node.get_absolute_url }}" target="_blank">{{ node.headline }}</a><br />
+                        <td>{{ node.friendly_name }}</td>
+                        {% declare %}
+                            is_root = node.abs_parent == None
+                            title = is_root and node.title or node.abs_parent.title
+
+                            anchor = "<strong>%s</strong>" % html.hyperlink(node.get_absolute_url(), title)
+                            anchor = ((not is_root) and node.abs_parent.nis.deleted) and "<span class=\"question-deleted\">%s</span>" % anchor or anchor
+                            anchor = is_root and anchor or "(%s)" % anchor
+                            anchor = html.mark_safe(anchor)
+
+                            td_class = ""
+                            td_class = node.nis.accepted and "accepted" or td_class
+                            td_class = node.nis.deleted and "deleted" or td_class
+                        {% enddeclare %}
+                        <td class="{{ td_class }}" colspan="2">
+                            {{ anchor }}<br />
                             {{ node.summary }}
                         </td>
                             {{ node.summary }}
                         </td>
-                        <td>{{ node.state_list|join:", " }}</td>
-                        <td><a href="{{ node.author.get_absolute_url  }}">{{ node.author.username }}</a></td>
+                        <td>
+                            {% for state in node.states.all %}
+                            <b>{{ state.state_type }}</b> {% diff_date state.action.at %} {% trans "by" %}
+                            <a target="_blank" href="{{ state.action.by.get_absolute_url }}">{{ state.action.by.decorated_name }}</a><br />
+                            {% endfor %}
+                        </td>
+                        <td><a href="{{ node.author.get_absolute_url  }}">{{ node.author.decorated_name }}</a></td>
                         <td>{% diff_date node.added_at %}</td>
                         <td>{% diff_date node.added_at %}</td>
-                        <td>{{ node.score }}</td>
-                        <td><a href="{{ node.last_activity_by.get_absolute_url  }}">{{ node.last_activity_by.username }}</a></td>
+                        <!--<td>{{ node.score }}</td>-->
+                        <td><a href="{{ node.last_activity_by.get_absolute_url  }}">{{ node.last_activity_by.decorated_name }}</a></td>
                         <td>{% diff_date node.last_activity_at %}</td>
                         <td>
                             {% for t in node.tags.all %}
                         <td>{% diff_date node.last_activity_at %}</td>
                         <td>
                             {% for t in node.tags.all %}
                         </td>
                     </tr>
                 {% endfor %}
                         </td>
                     </tr>
                 {% endfor %}
+                {% endwith %}
                 </tbody>
             </table>
             {{ nodes.paginator.page_numbers }}
                 </tbody>
             </table>
             {{ nodes.paginator.page_numbers }}
index 0fa533366c13d83d3c5157bd73d6015558170a68..40a098a3b470ff62938b7b8d1aaf49c342fab442 100644 (file)
@@ -10,7 +10,7 @@ from django.utils.translation import ugettext as _
 from django.utils import simplejson
 from django.db import models
 from forum.settings.base import Setting
 from django.utils import simplejson
 from django.db import models
 from forum.settings.base import Setting
-from forum.forms import MaintenanceModeForm, PageForm, NodeManFilterForm, CreateUserForm
+from forum.forms import MaintenanceModeForm, PageForm, CreateUserForm
 from forum.settings.forms import SettingsSetForm
 from forum.utils import pagination, html
 
 from forum.settings.forms import SettingsSetForm
 from forum.utils import pagination, html
 
@@ -412,113 +412,137 @@ class NodeManagementPaginatorContext(pagination.PaginatorContext):
         super (NodeManagementPaginatorContext, self).__init__(id, sort_methods=(
             (_('added_at'), pagination.SimpleSort(_('added_at'), '-added_at', "")),
             (_('added_at_asc'), pagination.SimpleSort(_('added_at_asc'), 'added_at', "")),
         super (NodeManagementPaginatorContext, self).__init__(id, sort_methods=(
             (_('added_at'), pagination.SimpleSort(_('added_at'), '-added_at', "")),
             (_('added_at_asc'), pagination.SimpleSort(_('added_at_asc'), 'added_at', "")),
+            (_('author'), pagination.SimpleSort(_('author'), '-author__username', "")),
+            (_('author_asc'), pagination.SimpleSort(_('author_asc'), 'author__username', "")),
             (_('score'), pagination.SimpleSort(_('score'), '-score', "")),
             (_('score_asc'), pagination.SimpleSort(_('score_asc'), 'score', "")),
             (_('act_at'), pagination.SimpleSort(_('act_at'), '-last_activity_at', "")),
             (_('act_at_asc'), pagination.SimpleSort(_('act_at_asc'), 'last_activity_at', "")),
             (_('score'), pagination.SimpleSort(_('score'), '-score', "")),
             (_('score_asc'), pagination.SimpleSort(_('score_asc'), 'score', "")),
             (_('act_at'), pagination.SimpleSort(_('act_at'), '-last_activity_at', "")),
             (_('act_at_asc'), pagination.SimpleSort(_('act_at_asc'), 'last_activity_at', "")),
+            (_('act_by'), pagination.SimpleSort(_('act_by'), '-last_activity_by__username', "")),
+            (_('act_by_asc'), pagination.SimpleSort(_('act_by_asc'), 'last_activity_by__username', "")),
         ), pagesizes=(default_pagesize,), force_sort='added_at', default_pagesize=default_pagesize, prefix=prefix)
 
 @admin_tools_page(_("nodeman"), _("Bulk management"))
 def node_management(request):
         ), pagesizes=(default_pagesize,), force_sort='added_at', default_pagesize=default_pagesize, prefix=prefix)
 
 @admin_tools_page(_("nodeman"), _("Bulk management"))
 def node_management(request):
-    if request.POST and "save_filter" in request.POST:
-        filter_name = request.POST.get('filter_name', _('filter'))
+    if request.POST:
         params = pagination.generate_uri(request.GET, ('page',))
         params = pagination.generate_uri(request.GET, ('page',))
-        current_filters = settings.NODE_MAN_FILTERS.value
-        current_filters.append((filter_name, params))
-        settings.NODE_MAN_FILTERS.set_value(current_filters)
 
 
-    if request.POST and "execute" in request.POST:
-        selected_nodes = request.POST.getlist('_selected_node')
+        if "save_filter" in request.POST:
+            filter_name = request.POST.get('filter_name', _('filter'))
+            params = pagination.generate_uri(request.GET, ('page',))
+            current_filters = settings.NODE_MAN_FILTERS.value
+            current_filters.append((filter_name, params))
+            settings.NODE_MAN_FILTERS.set_value(current_filters)
+
+        elif r"execute" in request.POST:
+            selected_nodes = request.POST.getlist('_selected_node')
 
 
-        if selected_nodes and request.POST.get('action', None):
-            action = request.POST['action']
-            selected_nodes = Node.objects.filter(id__in=selected_nodes)
+            if selected_nodes and request.POST.get('action', None):
+                action = request.POST['action']
+                selected_nodes = Node.objects.filter(id__in=selected_nodes)
 
 
-            message = _("No action performed")
+                message = _("No action performed")
 
 
-            if action == 'delete_selected':
-                for node in selected_nodes:
-                    if node.node_type in ('question', 'answer', 'comment') and (not node.nis.deleted):
-                        DeleteAction(user=request.user, node=node, ip=request.META['REMOTE_ADDR']).save()
-                        
-                message = _("All selected nodes marked as deleted")
+                if action == 'delete_selected':
+                    for node in selected_nodes:
+                        if node.node_type in ('question', 'answer', 'comment') and (not node.nis.deleted):
+                            DeleteAction(user=request.user, node=node, ip=request.META['REMOTE_ADDR']).save()
 
 
-            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 = _("All selected nodes marked as deleted")
 
 
-                message = _("Selected questions were closed")
+                if action == 'undelete_selected':
+                    for node in selected_nodes:
+                        if node.node_type in ('question', 'answer', 'comment') and (node.nis.deleted):
+                            node.nstate.deleted.cancel(ip=request.META['REMOTE_ADDR'])
 
 
-            if action == "hard_delete_selected":
-                ids = [n.id for n in selected_nodes]
+                    message = _("All selected nodes undeleted")
 
 
-                for id in ids:
-                    try:
-                        node = Node.objects.get(id=id)
-                        node.delete()
-                    except:
-                        pass                        
+                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 = _("All selected nodes deleted")
+                    message = _("Selected questions were closed")
 
 
-            request.user.message_set.create(message=message)
+                if action == "hard_delete_selected":
+                    ids = [n.id for n in selected_nodes]
 
 
-            params = pagination.generate_uri(request.GET, ('page',))
+                    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)
+
+                params = pagination.generate_uri(request.GET, ('page',))
+                
             return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}) + "?" + params)
 
 
     nodes = Node.objects.all()
 
             return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}) + "?" + params)
 
 
     nodes = Node.objects.all()
 
-    if (request.GET):
-        filter_form = NodeManFilterForm(request.GET)
-    else:
-        filter_form = NodeManFilterForm({'node_type': 'all', 'state_type': 'any'})
+    text = request.GET.get('text', '')
+    text_in = request.GET.get('text_in', 'body')
 
     authors = request.GET.getlist('authors')
     tags = request.GET.getlist('tags')
 
 
     authors = request.GET.getlist('authors')
     tags = request.GET.getlist('tags')
 
-    if filter_form.is_valid():
-        data = filter_form.cleaned_data
+    type_filter = request.GET.getlist('node_type')
+    state_filter = request.GET.getlist('state_type')
+    state_filter_type = request.GET.get('state_filter_type', 'any')
 
 
-        if data['node_type'] != 'all':
-            nodes = nodes.filter(node_type=data['node_type'])
+    if type_filter:
+        nodes = nodes.filter(node_type__in=type_filter)
 
 
-        if (data['state_type'] != 'any'):
-            nodes = nodes.filter_state(**{str(data['state_type']): True})
+    state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type')
+    state_filter = [s for s in state_filter if s in state_types]
 
 
-        if (authors):
-            nodes = nodes.filter(author__id__in=authors)
-            authors = User.objects.filter(id__in=authors)
+    if state_filter:
+        if state_filter_type == 'all':
+            nodes = nodes.all_states(*state_filter)
+        else:
+            nodes = nodes.any_state(*state_filter)
 
 
-        if (tags):
-            nodes = nodes.filter(tags__id__in=tags)
-            tags = Tag.objects.filter(id__in=tags)
+    if (authors):
+        nodes = nodes.filter(author__id__in=authors)
+        authors = User.objects.filter(id__in=authors)
 
 
-        if data['text']:
-            filter = None
+    if (tags):
+        nodes = nodes.filter(tags__id__in=tags)
+        tags = Tag.objects.filter(id__in=tags)
 
 
-            if data['text_in'] == 'title' or data['text_in'] == 'both':
-                filter = models.Q(title__icontains=data['text'])
+    if text:
+        text_in = request.GET.get('text_in', 'body')
+        filter = None
 
 
-            if data['text_in'] == 'body' or data['text_in'] == 'both':
-                sec_filter = models.Q(body__icontains=data['text'])
-                if filter:
-                    filter = filter | sec_filter
-                else:
-                    filter = sec_filter
+        if text_in == 'title' or text_in == 'both':
+            filter = models.Q(title__icontains=text)
 
 
+        if text_in == 'body' or text_in == 'both':
+            sec_filter = models.Q(body__icontains=text)
             if filter:
             if filter:
-                nodes = nodes.filter(filter)
+                filter = filter | sec_filter
+            else:
+                filter = sec_filter
 
 
-    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')
+        if filter:
+            nodes = nodes.filter(filter)
+
+    node_types = [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()]
 
     return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", NodeManagementPaginatorContext()), {
     'nodes': nodes,
 
     return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", NodeManagementPaginatorContext()), {
     'nodes': nodes,
+    'text': text,
+    'text_in': text_in,
+    'type_filter': type_filter,
+    'state_filter': state_filter,
+    'state_filter_type': state_filter_type,
     'node_types': node_types,
     'state_types': state_types,
     'node_types': node_types,
     'state_types': state_types,
-    'filter_form': filter_form,
     'authors': authors,
     'tags': tags,
     'hide_menu': True
     'authors': authors,
     'tags': tags,
     'hide_menu': True