Finish CSS template changes for Ticket page and related properties; includes responsive Ticket list table; styled file input buttons but need to find way to update text for beyond the first input box; added a ticket_attachment_del page to confirm removal of attachment and styled it

This commit is contained in:
Garret Wassermann 2016-09-12 02:11:55 -04:00
parent a9cb54ce7e
commit 00cdbcf43b
12 changed files with 259 additions and 154 deletions

View File

@ -1,5 +1,5 @@
This file contains license details for 3rd party software which is
distributed with Jutda Helpdesk.
distributed with django-helpdesk.
1. License for jQuery & jQuery UI
2. License for jQuery UI 'Smoothness' theme

View File

@ -77,7 +77,7 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm):
class Meta:
model = Ticket
exclude = ('created', 'modified', 'status', 'on_hold', 'resolution', 'last_escalation', 'assigned_to')
def __init__(self, *args, **kwargs):
"""
Add any custom fields that are defined to the form
@ -101,7 +101,7 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm):
def save(self, *args, **kwargs):
for field, value in self.cleaned_data.items():
if field.startswith('custom_'):
field_name = field.replace('custom_', '', 1)
@ -112,7 +112,7 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm):
cfv = TicketCustomFieldValue(ticket=self.instance, field=customfield)
cfv.value = value
cfv.save()
return super(EditTicketForm, self).save(*args, **kwargs)
@ -228,7 +228,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
except User.DoesNotExist:
t.assigned_to = None
t.save()
for field, value in self.cleaned_data.items():
if field.startswith('custom_'):
field_name = field.replace('custom_', '', 1)
@ -251,7 +251,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
}
f.save()
files = []
if self.cleaned_data['attachment']:
import mimetypes
@ -265,9 +265,9 @@ class TicketForm(CustomFieldMixin, forms.Form):
)
a.file.save(file.name, file, save=False)
a.save()
if file.size < getattr(settings, 'MAX_EMAIL_ATTACHMENT_SIZE', 512000):
# Only files smaller than 512kb (or as defined in
# Only files smaller than 512kb (or as defined in
# settings.MAX_EMAIL_ATTACHMENT_SIZE) are sent via email.
try:
files.append([a.filename, a.file])
@ -276,7 +276,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
context = safe_template_context(t)
context['comment'] = f.comment
messages_sent_to = []
if t.submitter_email:
@ -443,9 +443,9 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
)
a.file.save(file.name, file, save=False)
a.save()
if file.size < getattr(settings, 'MAX_EMAIL_ATTACHMENT_SIZE', 512000):
# Only files smaller than 512kb (or as defined in
# Only files smaller than 512kb (or as defined in
# settings.MAX_EMAIL_ATTACHMENT_SIZE) are sent via email.
files.append([a.filename, a.file])
@ -523,12 +523,11 @@ class UserSettingsForm(forms.Form):
required=False,
)
tickets_per_page = forms.IntegerField(
tickets_per_page = forms.ChoiceField(
label=_('Number of tickets to show per page'),
help_text=_('How many tickets do you want to see on the Ticket List page?'),
required=False,
min_value=1,
max_value=1000,
choices=((10,'10'),(25,'25'),(50,'50'),(100,'100')),
)
use_email_as_submitter = forms.BooleanField(
@ -543,17 +542,39 @@ class EmailIgnoreForm(forms.ModelForm):
exclude = []
class TicketCCForm(forms.ModelForm):
''' Adds either an email address or helpdesk user as a CC on a Ticket. Used for processing POST requests. '''
def __init__(self, *args, **kwargs):
super(TicketCCForm, self).__init__(*args, **kwargs)
if helpdesk_settings.HELPDESK_STAFF_ONLY_TICKET_CC:
users = User.objects.filter(is_active=True, is_staff=True).order_by(User.USERNAME_FIELD)
else:
users = User.objects.filter(is_active=True).order_by(User.USERNAME_FIELD)
self.fields['user'].queryset = users
self.fields['user'].queryset = users
class Meta:
model = TicketCC
exclude = ('ticket',)
class TicketCCUserForm(forms.ModelForm):
''' Adds a helpdesk user as a CC on a Ticket '''
def __init__(self, *args, **kwargs):
super(TicketCCUserForm, self).__init__(*args, **kwargs)
if helpdesk_settings.HELPDESK_STAFF_ONLY_TICKET_CC:
users = User.objects.filter(is_active=True, is_staff=True).order_by(User.USERNAME_FIELD)
else:
users = User.objects.filter(is_active=True).order_by(User.USERNAME_FIELD)
self.fields['user'].queryset = users
class Meta:
model = TicketCC
exclude = ('ticket','email',)
class TicketCCEmailForm(forms.ModelForm):
''' Adds an email address as a CC on a Ticket '''
def __init__(self, *args, **kwargs):
super(TicketCCEmailForm, self).__init__(*args, **kwargs)
class Meta:
model = TicketCC
exclude = ('ticket','user',)
class TicketDependencyForm(forms.ModelForm):
class Meta:
model = TicketDependency

View File

@ -2,6 +2,26 @@
Bootstrap overrides
*/
.btn-file {
position: relative;
overflow: hidden;
}
.btn-file input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 100px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
background: white;
cursor: inherit;
display: block;
}
.thumbnail.filterBox {
display: none;
float: left;
@ -52,4 +72,4 @@ Add your custom styles here
padding: 10px 0;
}
#helpdesk-body {padding-top: 100px;}
img.brand {padding-right: 30px;}
img.brand {padding-right: 30px;}

View File

@ -27,10 +27,6 @@
<!-- MetisMenu CSS -->
<link href="{% static 'helpdesk/vendor/metisMenu/metisMenu.min.css' %}" rel="stylesheet">
<!-- Custom CSS -->
<link href="{% static 'helpdesk/dist/css/sb-admin-2.css' %}" rel="stylesheet">
<link rel='stylesheet' href='{% static "helpdesk/helpdesk-extend.css" %}' type='text/css' media="screen" >
<!-- Morris Charts CSS -->
{% if helpdesk_settings.HELPDESK_USE_CDN %}
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css">
@ -38,6 +34,16 @@
<link href="{% static 'helpdesk/vendor/morrisjs/morris.css' %}" rel="stylesheet">
{% endif %}
<!-- DataTables CSS -->
<link href="{% static 'helpdesk/vendor/datatables-plugins/dataTables.bootstrap.css' %}" rel="stylesheet">
<!-- DataTables Responsive CSS -->
<link href="{% static 'helpdesk/vendor/datatables-responsive/dataTables.responsive.css' %}" rel="stylesheet">
<!-- Custom CSS -->
<link href="{% static 'helpdesk/dist/css/sb-admin-2.css' %}" rel="stylesheet">
<link rel='stylesheet' href='{% static "helpdesk/helpdesk-extend.css" %}' type='text/css' media="screen" >
<!-- Custom Fonts -->
<link href="{% static 'helpdesk/vendor/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet" type="text/css">
@ -47,7 +53,7 @@
{% else %}
<script src="{% static 'helpdesk/vendor/jquery/jquery.min.js' %}"></script>
{% endif %}
<script src='{% static "helpdesk/jquery-ui-1.12.0.min.js" %}' type='text/javascript' language='javascript'></script>
<!--<script src='{% static "helpdesk/jquery-ui-1.12.0.min.js" %}' type='text/javascript' language='javascript'></script>-->
<!-- Bootstrap Core JavaScript -->
{% if helpdesk_settings.HELPDESK_USE_CDN %}
@ -56,6 +62,14 @@
<script src="{% static 'helpdesk/vendor/bootstrap/js/bootstrap.min.js' %}"></script>
{% endif %}
<!-- DataTables JavaScript -->
<script src="{% static 'helpdesk/vendor/datatables/js/jquery.dataTables.js' %}"></script>
<script src="{% static 'helpdesk/vendor/datatables-plugins/dataTables.bootstrap.min.js' %}"></script>
<script src="{% static 'helpdesk/vendor/datatables-responsive/dataTables.responsive.js' %}"></script>
<!-- Custom Theme JavaScript -->
<script src="{% static 'helpdesk/dist/js/sb-admin-2.js' %}"></script>
<!-- RSS -->
<link rel='alternate' href='{% url 'helpdesk_rss_user' user.get_username %}' type='application/rss+xml' title='{% trans "My Open Tickets" %}' />
<link rel='alternate' href='{% url 'helpdesk_rss_activity' %}' type='application/rss+xml' title='{% trans "All Recent Activity" %}' />

View File

@ -28,7 +28,7 @@
<td><a href='{{ ticket.get_absolute_url }}'>{{ ticket.title }}</a></td>
<td>{{ ticket.queue }}</td>
<td><span title='{{ ticket.created|date:"r" }}'>{{ ticket.created|naturaltime }}</span></td>
<td><a href='{{ ticket.get_absolute_url }}?take'><span class='button button_take'>{% trans "Take" %}</span></a> | <a href='{% url 'helpdesk_delete' ticket.id %}'><span class='button button_delete'>{% trans "Delete" %}</span></a></td>
<td><a href='{{ ticket.get_absolute_url }}?take'><button class='btn btn-primary btn-xs'>{% trans "Take" %}</button></a> | <a href='{% url 'helpdesk_delete' ticket.id %}'><button class='btn btn-danger btn-xs'>{% trans "Delete" %}</button></a></td>
</tr>
{% empty %}
<tr><td colspan='6'>{% trans "There are no unassigned tickets." %}</td></tr>

View File

@ -11,7 +11,6 @@ $(document).ready(function() {
return false;
});
processAddFileClick();
$("#ShowFileUpload").click(function() {
$("#FileUpload").fadeIn();
$("#ShowFileUploadPara").hide();
@ -28,19 +27,37 @@ $(document).ready(function() {
});
$("[data-toggle=tooltip]").tooltip();
});
function processAddFileClick() {
/* Until jQuery includes some 'livequery' functionality in the core
distribution, this will have to do. */
$(".AddAnotherFile>a").click(function() {
$(this).parent().remove();
$("#FileUpload>dl").append("<dt><label>{% trans "Attach another File" %}</label></dt><dd><input type='file' name='attachment' id='file' multiple/> <span class='AddAnotherFile'>(<a href='#'>{% trans "Add Another File" %}</a>)</span></dd>");
processAddFileClick();
return false;
// for CSS customized file select/browse button
$(':file').on('fileselect', function(event, numFiles, label, browseButtonNum) {
$("#selectedfilename"+browseButtonNum).html(label);
});
}
var x = 0;
var wrapper = $(".input_fields_wrap"); //Fields wrapper
var add_button = $(".add_field_button"); //Add button ID
$(add_button).click(function(e){ //on add input button click
x++;
e.preventDefault();
$(wrapper).append("<div><label class='btn btn-primary btn-sm btn-file'>Browse... <input type='file' name='attachment' id='file" + x + "' multiple style='display: none;'/></label><span>&nbsp;</span><span id='selectedfilename" + x + "'>{% trans 'No files selected.' %}</span></div>"); //add input box
$(document).on('change', '#file'+x, function() {
var input = $(this),
numFiles = input.get(0).files ? input.get(0).files.length : 1,
label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
input.trigger('fileselect', [numFiles, label, x]);
});
});
});
$(document).on('change', '#file0', function() {
var input = $(this),
numFiles = input.get(0).files ? input.get(0).files.length : 1,
label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
input.trigger('fileselect', [numFiles, label, 0]);
});
</script>
{% endblock %}
@ -99,7 +116,7 @@ function processAddFileClick() {
{% for attachment in followup.attachment_set.all %}{% if forloop.first %}<div class='attachments'><ul>{% endif %}
<li><a href='{{ attachment.file.url }}'>{{ attachment.filename }}</a> ({{ attachment.mime_type }}, {{ attachment.size|filesizeformat }})
{% if followup.user and request.user == followup.user %}
<a href='{% url 'helpdesk_attachment_del' ticket.id attachment.id %}'>delete</a>
<a href='{% url 'helpdesk_attachment_del' ticket.id attachment.id %}'><button class="btn btn-danger btn-xs">{% trans 'Delete' %}</button></a>
{% endif %}
</li>
{% if forloop.last %}</ul></div>{% endif %}
@ -165,7 +182,7 @@ function processAddFileClick() {
{% endif %}
</dl>
<p id='ShowFurtherOptPara'><button id='ShowFurtherEditOptions'>{% trans "Change Further Details &raquo;" %}</button></p>
<p id='ShowFurtherOptPara'><button class="btn btn-warning btn-sm" id='ShowFurtherEditOptions'>{% trans "Change Further Details &raquo;" %}</button></p>
<div id='FurtherEditOptions' style='display: none;'>
@ -186,21 +203,29 @@ function processAddFileClick() {
</div>
<p id='ShowFileUploadPara'><button id='ShowFileUpload'>{% trans "Attach File(s) &raquo;" %}</button></p>
<p id='ShowFileUploadPara'><button class="btn btn-warning btn-sm" id='ShowFileUpload'>{% trans "Attach File(s) &raquo;" %}</button></p>
<div id='FileUpload' style='display: none;'>
<dl>
<dt><label for='id_file'>{% trans "Attach a File" %}</label></dt>
<dd><input type='file' name='attachment' id='file' multiple/> <span class='AddAnotherFile'>(<a href='#'>{% trans "Add Another File" %}</a>)</span></dd>
<dd>
<div class="input_fields_wrap">
<div>
<button class="add_field_button btn btn-success btn-xs">{% trans "Add Another File" %}</button>
<div><label class='btn btn-primary btn-sm btn-file'>
Browse... <input type="file" name='attachment' id='file0' style='display: none;'/>
</label><span>&nbsp;</span><span id='selectedfilename0'>{% trans 'No files selected.' %}</span></div>
</div>
</div>
</dd>
</dl>
</div>
</fieldset>
<input class="btn btn-primary" type='submit' value='{% trans "Update This Ticket" %}' />
<button class="btn btn-primary btn-lg" type='submit'>{% trans "Update This Ticket" %}</button>
{% csrf_token %}</form>

View File

@ -0,0 +1,16 @@
{% extends "helpdesk/base.html" %}{% load i18n %}
{% block helpdesk_title %}{% trans "Delete Ticket Attachment" %}{% endblock %}
{% block helpdesk_body %}{% blocktrans %}
<h2>Delete Ticket Attachment</h2>
<p>Are you sure you wish to delete the attachment <i>{{ filename }}</i> from this ticket? The attachment data will be permanently deleted from the database, but the attachment itself will still exist on the server.</p>
{% endblocktrans %}
<p><a href='../../'><button class="btn btn-primary btn-lg">{% trans "Don't Delete" %}</button></a></p>
<form method='post' action='./'>
<button class="btn btn-danger" type='submit'>{% trans "Yes, I Understand - Delete" %}&nbsp;{{ filename }}</button>
{% csrf_token %}</form>
{% endblock %}

View File

@ -2,24 +2,62 @@
{% block helpdesk_title %}{% trans "Add Ticket CC" %}{% endblock %}
{% block helpdesk_body %}{% blocktrans %}
<h2>Add Ticket CC</h2>
{% block helpdesk_body %}
<h2>{% trans 'Add Ticket CC' %}</h2>
<p>To automatically send an email to a user or e-mail address when this ticket is updated, select the user or enter an e-mail address below.</p>{% endblocktrans %}
<div class="row">
<div class="col-lg-12">
<div class="panel panel-primary">
<div class="panel-heading">
{% trans 'To automatically send an email to a user or e-mail address when this ticket is updated, select the user or enter an e-mail address below.' %}
</div>
<!-- /.panel-heading -->
<div class="panel-body">
<!-- Nav tabs -->
<ul class="nav nav-tabs">
<li class="active"><a href="#EmailCC" data-toggle="tab">Email</a>
</li>
<li><a href="#UserCC" data-toggle="tab">User</a>
</li>
</ul>
<form method='post' action='./'>
<fieldset>
<dl>{% for field in form %}
<dt><label for='id_{{ field.name }}'>{{ field.label }}</label></dt>
<dd>{{ field }}</dd>
{% if field.errors %}<dd class='error'>{{ field.errors }}</dd>{% endif %}
{% if field.help_text %}<dd class='form_help_text'>{{ field.help_text }}</dd>{% endif %}
{% endfor %}</dl>
</fieldset>
<input class="btn btn-primary" type='submit' value='{% trans "Save Ticket CC" %}' />
{% csrf_token %}</form>
<!-- Tab panes -->
<div class="tab-content">
<div class="tab-pane fade in active" id="EmailCC">
<h4>{% trans 'Add Email' %}</h4>
<form method='post' action='./'>
<fieldset>
<dl>{% for field in form_email %}
<dt><label for='id_{{ field.name }}'>{{ field.label }}</label></dt>
<dd>{{ field }}</dd>
{% if field.errors %}<dd class='error'>{{ field.errors }}</dd>{% endif %}
{% if field.help_text %}<dd class='form_help_text'>{{ field.help_text }}</dd>{% endif %}
{% endfor %}</dl>
</fieldset>
<button class="btn btn-primary" type='submit'>{% trans "Save Ticket CC" %}</button>
{% csrf_token %}</form>
</div>
<div class="tab-pane fade" id="UserCC">
<h4>{% trans 'Add User' %}</h4>
<form method='post' action='./'>
<fieldset>
<dl>{% for field in form_user %}
<dt><label for='id_{{ field.name }}'>{{ field.label }}</label></dt>
<dd>{{ field }}</dd>
{% if field.errors %}<dd class='error'>{{ field.errors }}</dd>{% endif %}
{% if field.help_text %}<dd class='form_help_text'>{{ field.help_text }}</dd>{% endif %}
{% endfor %}</dl>
</fieldset>
<button class="btn btn-primary" type='submit'>{% trans "Save Ticket CC" %}</button>
{% csrf_token %}</form>
</div>
</div>
</div>
<!-- /.panel-body -->
</div>
<!-- /.panel -->
</div>
</div>
<!-- /.row -->
{% endblock %}

View File

@ -8,7 +8,9 @@
<p>Are you sure you wish to remove the dependency on this ticket?</p>
{% endblocktrans %}
<p><a href='../../'>{% trans "Don't Delete" %}</a></p>
<p><a href='../../'><button class="btn btn-primary btn-lg">{% trans "Don't Delete" %}</button></a></p>
<form method='post' action='./'><input class="btn btn-primary" type='submit' value='{% trans "Yes, Delete" %}' />{% csrf_token %}</form>
<form method='post' action='./'>
<button class="btn btn-danger" type='submit'>{% trans "Yes, I Understand - Delete" %}</button>
{% csrf_token %}</form>
{% endblock %}

View File

@ -13,9 +13,9 @@
<table class="table table-striped table-bordered table-hover">
<thead>
<tr class='row_tablehead'><td colspan='2'><h3>{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}]</h3> <span class='ticket_toolbar'>
<a href='{% url 'helpdesk_edit' ticket.id %}' class="ticket-edit"><i class="fa fa-pencil"></i> {% trans "Edit" %}</a>
| <a href='{% url 'helpdesk_delete' ticket.id %}' class="ticket-delete"><i class="fa fa-trash-o"></i> {% trans "Delete" %}</a>
{% if ticket.on_hold %} | <a href='{% url 'helpdesk_unhold' ticket.id %}' class="ticket-hold"><i class="fa fa-play"></i> {% trans "Unhold" %}</a>{% else %} | <a href='{% url 'helpdesk_hold' ticket.id %}' class="ticket-hold"><i class="fa fa-pause"></i> {% trans "Hold" %}</a>{% endif %}
<a href="{% url 'helpdesk_edit' ticket.id %}" class="ticket-edit"><button class="btn btn-warning btn-xs"><i class="fa fa-pencil"></i> {% trans "Edit" %}</button></a>
| <a href="{% url 'helpdesk_delete' ticket.id %}" class="ticket-delete"><button class="btn btn-danger btn-xs"><i class="fa fa-trash-o"></i> {% trans "Delete" %}</button></a>
{% if ticket.on_hold %} | <a href="{% url 'helpdesk_unhold' ticket.id %}" class="ticket-hold"><button class="btn btn-warning btn-xs"><i class="fa fa-play"></i> {% trans "Unhold" %}</button></a>{% else %} | <a href="{% url 'helpdesk_hold' ticket.id %}" class="ticket-hold"><button class="btn btn-warning btn-xs"><i class="fa fa-pause"></i> {% trans "Hold" %}</button></a>{% endif %}
</span></td></tr>
<tr><th colspan='2'>{% blocktrans with ticket.queue as queue %}Queue: {{ queue }}{% endblocktrans %}</th></tr>
</thead>

View File

@ -3,17 +3,14 @@
{% load static from staticfiles %}
{% block helpdesk_title %}{% trans "Tickets" %}{% endblock %}
{% block helpdesk_head %}
<script type='text/javascript' language='javascript' src='{% static "helpdesk/filter.js" %}'></script>
<script type='text/javascript' language='javascript'>
$(document).ready(function() {
// Enable Tabs
$("#searchtabs").tabs({
collapsible:true
$('#ticketTable').DataTable({
responsive: true
});
// Hide all tabs by default
$("#searchtabs > ul > li").removeClass().addClass("ui-corner-top ui-state-default");
$("#searchtabs > div").addClass("ui-tabs-hide");
$("#select_all").click(function() {
$(".ticket_multi_select").attr('checked', true);
@ -213,68 +210,51 @@ $(document).ready(function() {
</div>
<!-- /.row -->
<div class="row">
<div class="col-lg-12">
<div class="panel panel-success">
<div class="panel panel-primary">
<div class="panel-heading">
{% trans "Query Results" %} - {{ search_message|safe }}
</div>
<!-- /.panel-heading -->
<div class="panel-body">
<div class="table-responsive">
<form method='post' action='{% url 'helpdesk_mass_update' %}' id="ticket_mass_update">
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>{% trans "Pr" %}</th>
<th>{% trans "Title" %}</th>
<th>{% trans "Queue" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Created" %}</th>
<th>{% trans "Owner" %}</th>
</tr>
</thead>
<tbody>
{% for ticket in tickets.object_list %}
<tr class="{{ ticket.get_priority_css_class }}">
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.ticket }}</a></th>
<td><input type='checkbox' name='ticket_id' value='{{ ticket.id }}' class='ticket_multi_select' /></td>
<td>{{ ticket.priority }}</td>
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.title }}</a></th>
<td>{{ ticket.queue }}</td>
<td>{{ ticket.get_status }}</td>
<td><span title='{{ ticket.created|date:"r" }}'>{{ ticket.created|naturaltime }}</span></td>
<td>{{ ticket.get_assigned_to }}</td>
</tr>
{% empty %}
<tr><td colspan='5'>{% trans "No Tickets Match Your Selection" %}</td></tr>
{% endfor %}
</tbody>
</table>
{% csrf_token %}</form>
<div class="pagination">
<span class="step-links">
{% if tickets.has_previous %}
<a href="?{% if query_string %}{{ query_string }}&amp;{% endif %}page={{ tickets.previous_page_number }}">{% trans "Previous" %}</a>
{% endif %}
<span class="current">
{% blocktrans with tickets.number as ticket_num and tickets.paginator.num_pages as num_pages %}Page {{ ticket_num }} of {{ num_pages }}.{% endblocktrans %}
</span>
{% if tickets.has_next %}
<a href="?{% if query_string %}{{ query_string }}&amp;{% endif %}page={{ tickets.next_page_number }}">{% trans "Next" %}</a>
{% endif %}
</span>
</div>
<form method='post' action='{% url 'helpdesk_mass_update' %}' id="ticket_mass_update">
<table width="100%" class="table table-striped table-bordered table-hover" id="ticketTable" data-page-length='{{ default_tickets_per_page }}'>
<thead>
<tr>
<th>#</th>
<th>&nbsp;</th>
<th>{% trans "Pr" %}</th>
<th>{% trans "Title" %}</th>
<th>{% trans "Queue" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Created" %}</th>
<th>{% trans "Owner" %}</th>
</tr>
</thead>
<tbody>
{% for ticket in tickets %}
<tr class="{{ ticket.get_priority_css_class }}">
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.ticket }}</a></th>
<td><input type='checkbox' name='ticket_id' value='{{ ticket.id }}' class='ticket_multi_select' /></td>
<td>{{ ticket.priority }}</td>
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.title }}</a></th>
<td>{{ ticket.queue }}</td>
<td>{{ ticket.get_status }}</td>
<td><span title='{{ ticket.created|date:"r" }}'>{{ ticket.created|naturaltime }}</span></td>
<td>{{ ticket.get_assigned_to }}</td>
</tr>
{% empty %}
<tr><td colspan='7'>{% trans "No Tickets Match Your Selection" %}</td></tr>
{% endfor %}
</tbody>
</table>
{% csrf_token %}</form>
<p><label>{% trans "Select:" %} </label> <a href='#select_all' id='select_all'><button class="btn btn-primary btn-sm">{% trans "All" %}</button></a> <a href='#select_none' id='select_none'><button class="btn btn-primary btn-sm">{% trans "None" %}</button></a> <a href='#select_inverse' id='select_inverse'><button class="btn btn-primary btn-sm">{% trans "Invert" %}</button></a></p>
<p><label for='id_mass_action'>{% trans "With Selected Tickets:" %}</label> <select name='action' id='id_mass_action'><option value='take'>{% trans "Take (Assign to me)" %}</option><option value='delete'>{% trans "Delete" %}</option><optgroup label='{% trans "Close" %}'><option value='close'>{% trans "Close (Don't Send E-Mail)" %}</option><option value='close_public'>{% trans "Close (Send E-Mail)" %}</option></optgroup><optgroup label='{% trans "Assign To" %}'><option value='unassign'>{% trans "Nobody (Unassign)" %}</option>{% for u in user_choices %}<option value='assign_{{ u.id }}'>{{ u.get_username }}</option>{% endfor %}</optgroup></select> <button class="btn btn-primary btn-sm">{% trans "Go" %}</button></p>
</div>
<!-- /.table-responsive -->
</div>
<!-- /.panel-body -->
</div>
@ -285,4 +265,5 @@ $(document).ready(function() {
<!-- /.row -->
{% endblock %}

View File

@ -38,7 +38,7 @@ try:
except ImportError:
from datetime import datetime as timezone
from helpdesk.forms import TicketForm, UserSettingsForm, EmailIgnoreForm, EditTicketForm, TicketCCForm, EditFollowUpForm, TicketDependencyForm
from helpdesk.forms import TicketForm, UserSettingsForm, EmailIgnoreForm, EditTicketForm, TicketCCForm, TicketCCEmailForm, TicketCCUserForm, EditFollowUpForm, TicketDependencyForm
from helpdesk.lib import send_templated_mail, query_to_dict, apply_query, safe_template_context
from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment, SavedSearch, IgnoreEmail, TicketCC, TicketDependency
from helpdesk import settings as helpdesk_settings
@ -424,12 +424,16 @@ def update_ticket(request, ticket_id, public=False):
files = []
if request.FILES:
import mimetypes, os
print(request.FILES)
print(request.FILES.getlist('attachment'))
for file in request.FILES.getlist('attachment'):
filename = file.name.encode('ascii', 'ignore')
filename = filename.decode("utf-8")
print(filename)
a = Attachment(
followup=f,
filename=filename,
mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream',
mime_type=file.content_type or 'application/octet-stream',
size=file.size,
)
a.file.save(filename, file, save=False)
@ -826,17 +830,6 @@ def ticket_list(request):
}
ticket_qs = apply_query(tickets, query_params)
ticket_paginator = paginator.Paginator(ticket_qs, request.user.usersettings.settings.get('tickets_per_page') or 20)
try:
page = int(request.GET.get('page', '1'))
except ValueError:
page = 1
try:
tickets = ticket_paginator.page(page)
except (paginator.EmptyPage, paginator.InvalidPage):
tickets = ticket_paginator.page(ticket_paginator.num_pages)
search_message = ''
if 'query' in context and settings.DATABASES['default']['ENGINE'].endswith('sqlite'):
search_message = _('<p><strong>Note:</strong> Your keyword search is case sensitive because of your database. This means the search will <strong>not</strong> be accurate. By switching to a different database system you will gain better searching! For more information, read the <a href="http://docs.djangoproject.com/en/dev/ref/databases/#sqlite-string-matching">Django Documentation on string matching in SQLite</a>.')
@ -848,15 +841,12 @@ def ticket_list(request):
user_saved_queries = SavedSearch.objects.filter(Q(user=request.user) | Q(shared__exact=True))
querydict = request.GET.copy()
querydict.pop('page', 1)
return render(request, 'helpdesk/ticket_list.html',
dict(
context,
query_string=querydict.urlencode(),
tickets=tickets,
tickets=ticket_qs,
default_tickets_per_page=request.user.usersettings.settings.get('tickets_per_page') or 25,
user_choices=User.objects.filter(is_active=True,is_staff=True),
queue_choices=user_queues,
status_choices=Ticket.STATUS_CHOICES,
@ -1339,11 +1329,13 @@ def ticket_cc_add(request, ticket_id):
ticketcc.save()
return HttpResponseRedirect(reverse('helpdesk_ticket_cc', kwargs={'ticket_id': ticket.id}))
else:
form = TicketCCForm()
form_email = TicketCCEmailForm()
form_user = TicketCCUserForm()
return render(request, template_name='helpdesk/ticket_cc_add.html',
context = {
'ticket': ticket,
'form': form,
'form_email': form_email,
'form_user': form_user,
})
ticket_cc_add = staff_member_required(ticket_cc_add)
@ -1395,9 +1387,16 @@ def attachment_del(request, ticket_id, attachment_id):
ticket = get_object_or_404(Ticket, id=ticket_id)
if not _has_access_to_queue(request.user, ticket.queue):
raise PermissionDenied()
attachment = get_object_or_404(Attachment, id=attachment_id)
attachment.delete()
return HttpResponseRedirect(reverse('helpdesk_view', args=[ticket_id]))
if request.method == 'POST':
attachment.delete()
return HttpResponseRedirect(reverse('helpdesk_view', args=[ticket_id]))
return render(request, template_name='helpdesk/ticket_attachment_del.html',
context = {
'attachment': attachment,
'filename': attachment.filename,
})
attachment_del = staff_member_required(attachment_del)
def calc_average_nbr_days_until_ticket_resolved(Tickets):
@ -1443,9 +1442,9 @@ def calc_basic_ticket_stats(Tickets):
# (O)pen (T)icket (S)tats
ots = list()
# label, number entries, color, sort_string
ots.append(['< 30 days', N_ota_le_30, get_color_for_nbr_days(N_ota_le_30), sort_string(date_30_str, ''), ])
ots.append(['30 - 60 days', N_ota_le_60_ge_30, get_color_for_nbr_days(N_ota_le_60_ge_30), sort_string(date_60_str, date_30_str), ])
ots.append(['> 60 days', N_ota_ge_60, get_color_for_nbr_days(N_ota_ge_60), sort_string('', date_60_str), ])
ots.append(['Tickets < 30 days', N_ota_le_30, 'success', sort_string(date_30_str, ''), ])
ots.append(['Tickets 30 - 60 days', N_ota_le_60_ge_30, 'success' if N_ota_le_60_ge_30 == 0 else 'warning', sort_string(date_60_str, date_30_str), ])
ots.append(['Tickets > 60 days', N_ota_ge_60, 'success' if N_ota_ge_60 == 0 else 'danger', sort_string('', date_60_str), ])
# all closed tickets - independent of user.
all_closed_tickets = Tickets.filter(status = Ticket.CLOSED_STATUS)
@ -1461,17 +1460,6 @@ def calc_basic_ticket_stats(Tickets):
return basic_ticket_stats
def get_color_for_nbr_days(nbr_days):
''' '''
if nbr_days < 5:
color_string = 'green'
elif nbr_days >= 5 and nbr_days < 10:
color_string = 'orange'
else: # more than 10 days
color_string = 'red'
return color_string
def days_since_created(today, ticket):
return (today - ticket.created).days