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
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()])
$(function() {
var $form = $('#changelist-search');
+ $('#all-node-type-link').click(function() {
+ $('#type-filter-container').find('input').remove();
+ $form.submit();
+ });
+
$('.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.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();
});
}
});
+ 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>
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" />
<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 }}" />
+ <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 }}" />
<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">
- <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>
+ <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 %}
- <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>
- <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 %}
- <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 %}
+ <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 %}
<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">
<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>
<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
+ 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
+ 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;" />
- </th>
- {% ifequal filter_form.node_type.data "all" %}
- <th>{% trans "Type" %}</th>
- {% endifequal %}
+ </th>
+ <th>{% trans "Type" %}</th>
<th>{% trans "Summary" %}</th>
+ <th class="col-resizer"></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 {{ score }}">
+ <!--<th class="sorted {{ score }}">
<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>{% trans "Last acivity by" %}</th>
<th class="sorted {{ act_at }}">
<a href="{{ act_at_link }}">{% trans "Last activity at" %}</a>
</th>
</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>
- {% 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>
- <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>{{ 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>
</tr>
{% endfor %}
+ {% endwith %}
</tbody>
</table>
{{ nodes.paginator.page_numbers }}
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
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', "")),
+ (_('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):
- 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',))
- 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()
- 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')
- 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:
- 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,
+ '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,
- 'filter_form': filter_form,
'authors': authors,
'tags': tags,
'hide_menu': True