Merge upstream changes

This commit is contained in:
Tony Zhu 2015-04-14 16:10:13 -04:00
commit 11e4f1a7bd
27 changed files with 614 additions and 61 deletions

View File

@ -2,6 +2,7 @@ include README
include UPGRADE include UPGRADE
include LICENSE* include LICENSE*
include CHANGELOG include CHANGELOG
include requirements.txt
recursive-include helpdesk/static/helpdesk * recursive-include helpdesk/static/helpdesk *
recursive-include helpdesk/locale *.po *.mo recursive-include helpdesk/locale *.po *.mo

View File

@ -23,7 +23,7 @@ Before django-helpdesk will be much use, you need to do some basic configuration
0 * * * * /path/to/helpdesksite/manage.py escalate_tickets 0 * * * * /path/to/helpdesksite/manage.py escalate_tickets
This will run the escalation process hourly, using the 'Escalation Hours' setting for each queue to determine which tickets to escalate. This will run the escalation process hourly, using the 'Escalation Days' setting for each queue to determine which tickets to escalate.
5. If you wish to exclude some days (eg, weekends) from escalation calculations, enter the dates manually via the Admin, or setup a cronjob to run a management command on a regular basis:: 5. If you wish to exclude some days (eg, weekends) from escalation calculations, enter the dates manually via the Admin, or setup a cronjob to run a management command on a regular basis::
@ -39,4 +39,6 @@ Before django-helpdesk will be much use, you need to do some basic configuration
EMAIL_HOST_USER = 'YYYYYY@ZZZZ.PPP' EMAIL_HOST_USER = 'YYYYYY@ZZZZ.PPP'
EMAIL_HOST_PASSWORD = '123456' EMAIL_HOST_PASSWORD = '123456'
8. If you wish to use SOCKS4/5 proxy with Helpdesk Queue email operations, install PySocks manually.
You're now up and running! Happy ticketing. You're now up and running! Happy ticketing.

View File

@ -29,7 +29,7 @@ Download, extract, and drop ``helpdesk`` into your ``PYTHONPATH``
Adding To Your Django Project Adding To Your Django Project
----------------------------- -----------------------------
1. Edit your ``settings.py`` file and add ``helpdesk`` to the ``INSTALLED_APPS`` setting. You also need ``django.contrib.admin`` and ``django.contrib.markup`` in ``INSTALLED_APPS`` if you haven't already added it. eg:: 1. Edit your ``settings.py`` file and add ``helpdesk`` to the ``INSTALLED_APPS`` setting. You also need ``django.contrib.admin`` in ``INSTALLED_APPS`` if you haven't already added it. eg::
INSTALLED_APPS = ( INSTALLED_APPS = (
'django.contrib.auth', 'django.contrib.auth',
@ -37,11 +37,11 @@ Adding To Your Django Project
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.sites', 'django.contrib.sites',
'django.contrib.admin', # Required for helpdesk admin/maintenance 'django.contrib.admin', # Required for helpdesk admin/maintenance
'django.contrib.markup', # Required for helpdesk text display
'django.contrib.humanize', # Required for elapsed time formatting 'django.contrib.humanize', # Required for elapsed time formatting
'south', # Highly recommended to make database migrations simpler. 'south', # Highly recommended to make database migrations simpler in Django < 1.7
'markdown_deux', # Required for Knowledgebase item formatting 'markdown_deux', # Required for Knowledgebase item formatting
'helpdesk', # This is new! 'bootstrapform', # Required for nicer formatting of forms with the default templates
'helpdesk', # This is us!
) )
2. Make sure django-helpdesk is accessible via ``urls.py``. Add the following line to ``urls.py``:: 2. Make sure django-helpdesk is accessible via ``urls.py``. Add the following line to ``urls.py``::
@ -58,7 +58,7 @@ Adding To Your Django Project
./manage.py syncdb ./manage.py syncdb
Then migrate using South Then migrate using South / Django 1.7+ migrations::
./manage.py migrate helpdesk ./manage.py migrate helpdesk
@ -98,3 +98,7 @@ Adding To Your Django Project
LOGIN_URL = '/helpdesk/login/' LOGIN_URL = '/helpdesk/login/'
Alter the URL to suit your installation path. Alter the URL to suit your installation path.
9. Load initial e-mail templates, otherwise you will not be able to setnd e-mail::
python manage.py loaddata emailtemplate.json

View File

@ -102,7 +102,7 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm):
for field, value in self.cleaned_data.items(): for field, value in self.cleaned_data.items():
if field.startswith('custom_'): if field.startswith('custom_'):
field_name = field.replace('custom_', '') field_name = field.replace('custom_', '', 1)
customfield = CustomField.objects.get(name=field_name) customfield = CustomField.objects.get(name=field_name)
try: try:
cfv = TicketCustomFieldValue.objects.get(ticket=self.instance, field=customfield) cfv = TicketCustomFieldValue.objects.get(ticket=self.instance, field=customfield)
@ -229,7 +229,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
for field, value in self.cleaned_data.items(): for field, value in self.cleaned_data.items():
if field.startswith('custom_'): if field.startswith('custom_'):
field_name = field.replace('custom_', '') field_name = field.replace('custom_', '', 1)
customfield = CustomField.objects.get(name=field_name) customfield = CustomField.objects.get(name=field_name)
cfv = TicketCustomFieldValue(ticket=t, cfv = TicketCustomFieldValue(ticket=t,
field=customfield, field=customfield,
@ -369,6 +369,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
required=False, required=False,
label=_('Attach File'), label=_('Attach File'),
help_text=_('You can attach a file such as a document or screenshot to this ticket.'), help_text=_('You can attach a file such as a document or screenshot to this ticket.'),
max_length=1000,
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -407,7 +408,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
for field, value in self.cleaned_data.items(): for field, value in self.cleaned_data.items():
if field.startswith('custom_'): if field.startswith('custom_'):
field_name = field.replace('custom_', '') field_name = field.replace('custom_', '', 1)
customfield = CustomField.objects.get(name=field_name) customfield = CustomField.objects.get(name=field_name)
cfv = TicketCustomFieldValue(ticket=t, cfv = TicketCustomFieldValue(ticket=t,
field=customfield, field=customfield,
@ -529,9 +530,9 @@ class TicketCCForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TicketCCForm, self).__init__(*args, **kwargs) super(TicketCCForm, self).__init__(*args, **kwargs)
if helpdesk_settings.HELPDESK_STAFF_ONLY_TICKET_CC: if helpdesk_settings.HELPDESK_STAFF_ONLY_TICKET_CC:
users = User.objects.filter(is_active=True, is_staff=True).order_by('username') users = User.objects.filter(is_active=True, is_staff=True).order_by(User.USERNAME_FIELD)
else: else:
users = User.objects.filter(is_active=True).order_by('username') users = User.objects.filter(is_active=True).order_by(User.USERNAME_FIELD)
self.fields['user'].queryset = users self.fields['user'].queryset = users
class Meta: class Meta:
model = TicketCC model = TicketCC

View File

@ -115,7 +115,7 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b
elif type(recipients) != list: elif type(recipients) != list:
recipients = [recipients,] recipients = [recipients,]
msg = EmailMultiAlternatives( subject_part.replace('\n', ''), msg = EmailMultiAlternatives( subject_part.replace('\n', '').replace('\r', ''),
text_part, text_part,
sender, sender,
recipients, recipients,

View File

@ -15,6 +15,7 @@ import imaplib
import mimetypes import mimetypes
import poplib import poplib
import re import re
import socket
from datetime import timedelta from datetime import timedelta
from email.header import decode_header from email.header import decode_header
@ -84,6 +85,22 @@ def process_queue(q, quiet=False):
if not quiet: if not quiet:
print "Processing: %s" % q print "Processing: %s" % q
if q.socks_proxy_type and q.socks_proxy_host and q.socks_proxy_port:
try:
import socks
except ImportError:
raise ImportError("Queue has been configured with proxy settings, but no socks library was installed. Try to install PySocks via pypi.")
proxy_type = {
'socks4': socks.SOCKS4,
'socks5': socks.SOCKS5,
}.get(q.socks_proxy_type)
socks.set_default_proxy(proxy_type=proxy_type, addr=q.socks_proxy_host, port=q.socks_proxy_port)
socket.socket = socks.socksocket
else:
socket.socket = socket._socketobject
email_box_type = settings.QUEUE_EMAIL_BOX_TYPE if settings.QUEUE_EMAIL_BOX_TYPE else q.email_box_type email_box_type = settings.QUEUE_EMAIL_BOX_TYPE if settings.QUEUE_EMAIL_BOX_TYPE else q.email_box_type
if email_box_type == 'pop3': if email_box_type == 'pop3':

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('helpdesk', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='queue',
name='socks_proxy_host',
field=models.GenericIPAddressField(help_text='Socks proxy IP address. Default: 127.0.0.1', null=True, verbose_name='Socks Proxy Host', blank=True),
preserve_default=True,
),
migrations.AddField(
model_name='queue',
name='socks_proxy_port',
field=models.IntegerField(help_text='Socks proxy port number. Default: 9150 (default TOR port)', null=True, verbose_name='Socks Proxy Port', blank=True),
preserve_default=True,
),
migrations.AddField(
model_name='queue',
name='socks_proxy_type',
field=models.CharField(choices=[(b'socks4', 'SOCKS4'), (b'socks5', 'SOCKS5')], max_length=8, blank=True, help_text='SOCKS4 or SOCKS5 allows you to proxy your connections through a SOCKS server.', null=True, verbose_name='Socks Proxy Type'),
preserve_default=True,
),
]

View File

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib.auth import get_user_model
from django.db import models, migrations
from helpdesk.settings import DEFAULT_USER_SETTINGS
def picke_settings(data):
"""Pickling as defined at migration's creation time"""
import cPickle
from helpdesk.lib import b64encode
return b64encode(cPickle.dumps(data))
# https://docs.djangoproject.com/en/1.7/topics/migrations/#data-migrations
def populate_usersettings(apps, schema_editor):
"""Create a UserSettings entry for each existing user.
This will only happen once (at install time, or at upgrade)
when the UserSettings model doesn't already exist."""
_User = get_user_model()
User = apps.get_model(_User._meta.app_label, _User._meta.model_name)
# Import historical version of models
UserSettings = apps.get_model("helpdesk", "UserSettings")
settings_pickled = picke_settings(DEFAULT_USER_SETTINGS)
for u in User.objects.all():
try:
UserSettings.objects.get(user=u)
except UserSettings.DoesNotExist:
UserSettings.objects.create(user=u, settings_pickled=settings_pickled)
noop = lambda *args, **kwargs: None
class Migration(migrations.Migration):
dependencies = [
('helpdesk', '0002_socks_proxy'),
]
operations = [
migrations.RunPython(populate_usersettings, reverse_code=noop),
]

View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
from sys import path
from django.db import models, migrations
from django.core import serializers
fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures'))
fixture_filename = 'emailtemplate.json'
def deserialize_fixture():
fixture_file = os.path.join(fixture_dir, fixture_filename)
with open(fixture_file, 'rb') as fixture:
return list(serializers.deserialize('json', fixture, ignorenonexistent=True))
def load_fixture(apps, schema_editor):
objects = deserialize_fixture()
for obj in objects:
obj.save()
def unload_fixture(apps, schema_editor):
"Delete all EmailTemplate objects"
objects = deserialize_fixture()
EmailTemplate = apps.get_model("helpdesk", "emailtemplate")
EmailTemplate.objects.filter(pk__in=[ obj.object.pk for obj in objects ]).delete()
class Migration(migrations.Migration):
dependencies = [
('helpdesk', '0003_populate_usersettings'),
]
operations = [
migrations.RunPython(load_fixture, reverse_code=unload_fixture),
]

View File

@ -11,6 +11,7 @@ from django.db import models
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _, ugettext from django.utils.translation import ugettext_lazy as _, ugettext
from django import VERSION
try: try:
from django.utils import timezone from django.utils import timezone
@ -178,6 +179,29 @@ class Queue(models.Model):
# This is updated by management/commands/get_mail.py. # This is updated by management/commands/get_mail.py.
) )
socks_proxy_type = models.CharField(
_('Socks Proxy Type'),
max_length=8,
choices=(('socks4', _('SOCKS4')), ('socks5', _('SOCKS5'))),
blank=True,
null=True,
help_text=_('SOCKS4 or SOCKS5 allows you to proxy your connections through a SOCKS server.'),
)
socks_proxy_host = models.GenericIPAddressField(
_('Socks Proxy Host'),
blank=True,
null=True,
help_text=_('Socks proxy IP address. Default: 127.0.0.1'),
)
socks_proxy_port = models.IntegerField(
_('Socks Proxy Port'),
blank=True,
null=True,
help_text=_('Socks proxy port number. Default: 9150 (default TOR port)'),
)
def __unicode__(self): def __unicode__(self):
return u"%s" % self.title return u"%s" % self.title
@ -202,6 +226,15 @@ class Queue(models.Model):
if self.email_box_type == 'imap' and not self.email_box_imap_folder: if self.email_box_type == 'imap' and not self.email_box_imap_folder:
self.email_box_imap_folder = 'INBOX' self.email_box_imap_folder = 'INBOX'
if self.socks_proxy_type:
if not self.socks_proxy_host:
self.socks_proxy_host = '127.0.0.1'
if not self.socks_proxy_port:
self.socks_proxy_port = 9150
else:
self.socks_proxy_host = None
self.socks_proxy_port = None
if not self.email_box_port: if not self.email_box_port:
if self.email_box_type == 'imap' and self.email_box_ssl: if self.email_box_type == 'imap' and self.email_box_ssl:
self.email_box_port = 993 self.email_box_port = 993
@ -351,7 +384,7 @@ class Ticket(models.Model):
if self.assigned_to.get_full_name(): if self.assigned_to.get_full_name():
return self.assigned_to.get_full_name() return self.assigned_to.get_full_name()
else: else:
return self.assigned_to.username return self.assigned_to.get_username()
get_assigned_to = property(_get_assigned_to) get_assigned_to = property(_get_assigned_to)
def _get_ticket(self): def _get_ticket(self):
@ -995,7 +1028,7 @@ class UserSettings(models.Model):
verbose_name_plural = _('User Settings') verbose_name_plural = _('User Settings')
def create_usersettings(sender, created_models=[], instance=None, created=False, **kwargs): def create_usersettings(sender, instance, created, **kwargs):
""" """
Helper function to create UserSettings instances as Helper function to create UserSettings instances as
required, eg when we first create the UserSettings database required, eg when we first create the UserSettings database
@ -1005,30 +1038,20 @@ def create_usersettings(sender, created_models=[], instance=None, created=False,
'DoesNotExist: UserSettings matching query does not exist.' errors. 'DoesNotExist: UserSettings matching query does not exist.' errors.
""" """
from helpdesk.settings import DEFAULT_USER_SETTINGS from helpdesk.settings import DEFAULT_USER_SETTINGS
if sender == settings.AUTH_USER_MODEL and created: if created:
# This is a new user, so lets create their settings entry. UserSettings.objects.create(user=instance, settings=DEFAULT_USER_SETTINGS)
s, created = UserSettings.objects.get_or_create(user=instance, defaults={'settings': DEFAULT_USER_SETTINGS})
s.save()
elif UserSettings in created_models:
User = get_user_model()
# We just created the UserSettings model, lets create a UserSettings
# entry for each existing user. This will only happen once (at install
# time, or at upgrade) when the UserSettings model doesn't already
# exist.
for u in User.objects.all():
try:
s = UserSettings.objects.get(user=u)
except UserSettings.DoesNotExist:
s = UserSettings(user=u, settings=DEFAULT_USER_SETTINGS)
s.save()
models.signals.post_syncdb.connect(create_usersettings)
try: try:
# Connecting via settings.AUTH_USER_MODEL (string) fails in Django < 1.7. We need the actual model there.
# https://docs.djangoproject.com/en/1.7/topics/auth/customizing/#referencing-the-user-model
if VERSION < (1, 7):
raise ValueError
models.signals.post_save.connect(create_usersettings, sender=settings.AUTH_USER_MODEL) models.signals.post_save.connect(create_usersettings, sender=settings.AUTH_USER_MODEL)
except: except:
signal_user = get_user_model() signal_user = get_user_model()
models.signals.post_save.connect(create_usersettings, sender=signal_user) models.signals.post_save.connect(create_usersettings, sender=signal_user)
class IgnoreEmail(models.Model): class IgnoreEmail(models.Model):
""" """
This model lets us easily ignore e-mails from certain senders when This model lets us easily ignore e-mails from certain senders when

View File

@ -0,0 +1,249 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Queue.socks_proxy_type'
db.add_column(u'helpdesk_queue', 'socks_proxy_type',
self.gf('django.db.models.fields.CharField')(max_length=8, null=True, blank=True),
keep_default=False)
# Adding field 'Queue.socks_proxy_host'
db.add_column(u'helpdesk_queue', 'socks_proxy_host',
self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39, null=True, blank=True),
keep_default=False)
# Adding field 'Queue.socks_proxy_port'
db.add_column(u'helpdesk_queue', 'socks_proxy_port',
self.gf('django.db.models.fields.IntegerField')(null=True, blank=True),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Queue.socks_proxy_type'
db.delete_column(u'helpdesk_queue', 'socks_proxy_type')
# Deleting field 'Queue.socks_proxy_host'
db.delete_column(u'helpdesk_queue', 'socks_proxy_host')
# Deleting field 'Queue.socks_proxy_port'
db.delete_column(u'helpdesk_queue', 'socks_proxy_port')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'helpdesk.attachment': {
'Meta': {'ordering': "['filename']", 'object_name': 'Attachment'},
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '1000'}),
'filename': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
'followup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['helpdesk.FollowUp']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'size': ('django.db.models.fields.IntegerField', [], {})
},
u'helpdesk.customfield': {
'Meta': {'object_name': 'CustomField'},
'data_type': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'decimal_places': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'empty_selection_list': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'help_text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'label': ('django.db.models.fields.CharField', [], {'max_length': "'30'"}),
'list_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'max_length': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}),
'ordering': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'staff_only': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
},
u'helpdesk.emailtemplate': {
'Meta': {'ordering': "['template_name', 'locale']", 'object_name': 'EmailTemplate'},
'heading': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'html': ('django.db.models.fields.TextField', [], {}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'locale': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
'plain_text': ('django.db.models.fields.TextField', [], {}),
'subject': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'template_name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'helpdesk.escalationexclusion': {
'Meta': {'object_name': 'EscalationExclusion'},
'date': ('django.db.models.fields.DateField', [], {}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'queues': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['helpdesk.Queue']", 'null': 'True', 'blank': 'True'})
},
u'helpdesk.followup': {
'Meta': {'ordering': "['date']", 'object_name': 'FollowUp'},
'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'new_status': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['helpdesk.Ticket']"}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
},
u'helpdesk.ignoreemail': {
'Meta': {'object_name': 'IgnoreEmail'},
'date': ('django.db.models.fields.DateField', [], {'blank': 'True'}),
'email_address': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'keep_in_mailbox': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'queues': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['helpdesk.Queue']", 'null': 'True', 'blank': 'True'})
},
u'helpdesk.kbcategory': {
'Meta': {'ordering': "['title']", 'object_name': 'KBCategory'},
'description': ('django.db.models.fields.TextField', [], {}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'helpdesk.kbitem': {
'Meta': {'ordering': "['title']", 'object_name': 'KBItem'},
'answer': ('django.db.models.fields.TextField', [], {}),
'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['helpdesk.KBCategory']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_updated': ('django.db.models.fields.DateTimeField', [], {'blank': 'True'}),
'question': ('django.db.models.fields.TextField', [], {}),
'recommendations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'votes': ('django.db.models.fields.IntegerField', [], {'default': '0'})
},
u'helpdesk.presetreply': {
'Meta': {'ordering': "['name']", 'object_name': 'PreSetReply'},
'body': ('django.db.models.fields.TextField', [], {}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'queues': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['helpdesk.Queue']", 'null': 'True', 'blank': 'True'})
},
u'helpdesk.queue': {
'Meta': {'ordering': "('title',)", 'object_name': 'Queue'},
'allow_email_submission': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'allow_public_submission': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
'email_box_host': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'email_box_imap_folder': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
'email_box_interval': ('django.db.models.fields.IntegerField', [], {'default': "'5'", 'null': 'True', 'blank': 'True'}),
'email_box_last_check': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'email_box_pass': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'email_box_port': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'email_box_ssl': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'email_box_type': ('django.db.models.fields.CharField', [], {'max_length': '5', 'null': 'True', 'blank': 'True'}),
'email_box_user': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'escalate_days': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'locale': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
'new_ticket_cc': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
'socks_proxy_host': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}),
'socks_proxy_port': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'socks_proxy_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'updated_ticket_cc': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
},
u'helpdesk.savedsearch': {
'Meta': {'object_name': 'SavedSearch'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'query': ('django.db.models.fields.TextField', [], {}),
'shared': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
},
u'helpdesk.ticket': {
'Meta': {'ordering': "('id',)", 'object_name': 'Ticket'},
'assigned_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'assigned_to'", 'null': 'True', 'to': u"orm['auth.User']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'due_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_escalation': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'blank': 'True'}),
'on_hold': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'priority': ('django.db.models.fields.IntegerField', [], {'default': '3', 'blank': '3'}),
'queue': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['helpdesk.Queue']"}),
'resolution': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'status': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'submitter_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '200'})
},
u'helpdesk.ticketcc': {
'Meta': {'object_name': 'TicketCC'},
'can_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'can_view': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['helpdesk.Ticket']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
},
u'helpdesk.ticketchange': {
'Meta': {'object_name': 'TicketChange'},
'field': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'followup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['helpdesk.FollowUp']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'new_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'old_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
},
u'helpdesk.ticketcustomfieldvalue': {
'Meta': {'object_name': 'TicketCustomFieldValue'},
'field': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['helpdesk.CustomField']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['helpdesk.Ticket']"}),
'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
},
u'helpdesk.ticketdependency': {
'Meta': {'unique_together': "(('ticket', 'depends_on'),)", 'object_name': 'TicketDependency'},
'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'depends_on'", 'to': u"orm['helpdesk.Ticket']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ticket': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ticketdependency'", 'to': u"orm['helpdesk.Ticket']"})
},
u'helpdesk.usersettings': {
'Meta': {'object_name': 'UserSettings'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'settings_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
}
}
complete_apps = ['helpdesk']

View File

@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
from django.contrib.auth import get_user_model
from helpdesk.settings import DEFAULT_USER_SETTINGS
def pickle_settings(data):
"""Pickling as defined at migration's creation time"""
import cPickle
from helpdesk.lib import b64encode
return b64encode(cPickle.dumps(data))
# https://docs.djangoproject.com/en/1.7/topics/migrations/#data-migrations
def populate_usersettings(orm):
"""Create a UserSettings entry for each existing user.
This will only happen once (at install time, or at upgrade)
when the UserSettings model doesn't already exist."""
_User = get_user_model()
# Import historical version of models
User = orm[_User._meta.app_label+'.'+_User._meta.model_name]
UserSettings = orm["helpdesk"+'.'+"UserSettings"]
settings_pickled = pickle_settings(DEFAULT_USER_SETTINGS)
for u in User.objects.all():
try:
UserSettings.objects.get(user=u)
except UserSettings.DoesNotExist:
UserSettings.objects.create(user=u, settings_pickled=settings_pickled)
class Migration(DataMigration):
def forwards(self, orm):
populate_usersettings(orm)
def backwards(self, orm):
pass
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'helpdesk.usersettings': {
'Meta': {'object_name': 'UserSettings'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'settings_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
}
}
complete_apps = ['helpdesk']
symmetrical = True

View File

@ -17,7 +17,7 @@
<!--link rel='stylesheet' href='{{ STATIC_URL }}helpdesk/jquery-smoothness-theme/jquery-ui-1.8.9.custom.css' type='text/css' /--> <!--link rel='stylesheet' href='{{ STATIC_URL }}helpdesk/jquery-smoothness-theme/jquery-ui-1.8.9.custom.css' type='text/css' /-->
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.2/js/bootstrap.min.js"></script> <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.2/js/bootstrap.min.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css" rel="stylesheet"> <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css" rel="stylesheet">
<link rel='alternate' href='{% url 'helpdesk_rss_user' user.username %}' type='application/rss+xml' title='{% trans "My Open Tickets" %}' /> <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" %}' /> <link rel='alternate' href='{% url 'helpdesk_rss_activity' %}' type='application/rss+xml' title='{% trans "All Recent Activity" %}' />
<link rel='alternate' href='{% url 'helpdesk_rss_unassigned' %}' type='application/rss+xml' title='{% trans "Unassigned Tickets" %}' /> <link rel='alternate' href='{% url 'helpdesk_rss_unassigned' %}' type='application/rss+xml' title='{% trans "Unassigned Tickets" %}' />

View File

@ -26,13 +26,13 @@
{% for q in user_saved_queries_ %} {% for q in user_saved_queries_ %}
<li><a href="{% url 'helpdesk_list' %}?saved_query={{ q.id }}">{{ q.title }} <li><a href="{% url 'helpdesk_list' %}?saved_query={{ q.id }}">{{ q.title }}
{% if q.shared %} {% if q.shared %}
(Shared{% ifnotequal user q.user %} by {{ q.user.username }}{% endifnotequal %}) (Shared{% ifnotequal user q.user %} by {{ q.user.get_username }}{% endifnotequal %})
{% endif %}</a></li> {% endif %}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</li> </li>
{% endif %} {% endif %}
<li class="headerlink dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#"><span class="glyphicon glyphicon-user"></span> <span class="nav-text">{{ user.get_full_name|default:user.username }} <b class="caret"></b></span></a> <li class="headerlink dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#"><span class="glyphicon glyphicon-user"></span> <span class="nav-text">{{ user.get_full_name|default:user.get_username }} <b class="caret"></b></span></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href='{% url 'helpdesk_user_settings' %}'>{% trans "User Settings" %}</a></li> <li><a href='{% url 'helpdesk_user_settings' %}'>{% trans "User Settings" %}</a></li>
{% if helpdesk_settings.HELPDESK_SHOW_CHANGE_PASSWORD and user.has_usable_password %} {% if helpdesk_settings.HELPDESK_SHOW_CHANGE_PASSWORD and user.has_usable_password %}

View File

@ -6,7 +6,7 @@
<p>{% trans "The following RSS feeds are available for you to monitor using your preferred RSS software. With the exception of the 'Latest Activity' feed, all feeds provide information only on Open and Reopened cases. This ensures your RSS reader isn't full of information about closed or historical tasks." %}</p> <p>{% trans "The following RSS feeds are available for you to monitor using your preferred RSS software. With the exception of the 'Latest Activity' feed, all feeds provide information only on Open and Reopened cases. This ensures your RSS reader isn't full of information about closed or historical tasks." %}</p>
<dl> <dl>
<dt><a href='{% url 'helpdesk_rss_user' user.username %}'><img src='{{ STATIC_URL }}helpdesk/rss_icon.png' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "My Open Tickets" %}' border='0' />{% trans "My Open Tickets" %}</a></dt> <dt><a href='{% url 'helpdesk_rss_user' user.get_username %}'><img src='{{ STATIC_URL }}helpdesk/rss_icon.png' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "My Open Tickets" %}' border='0' />{% trans "My Open Tickets" %}</a></dt>
<dd>{% trans "A summary of your open tickets - useful for getting alerted to new tickets opened for you" %}</dd> <dd>{% trans "A summary of your open tickets - useful for getting alerted to new tickets opened for you" %}</dd>
<dt><a href='{% url 'helpdesk_rss_activity' %}'><img src='{{ STATIC_URL }}helpdesk/rss_icon.png' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "Latest Activity" %}' border='0' />{% trans "Latest Activity" %}</a></dt> <dt><a href='{% url 'helpdesk_rss_activity' %}'><img src='{{ STATIC_URL }}helpdesk/rss_icon.png' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "Latest Activity" %}' border='0' />{% trans "Latest Activity" %}</a></dt>
@ -28,7 +28,7 @@
<tr> <tr>
<td>{{ queue.title }}</td> <td>{{ queue.title }}</td>
<td align='center'><a href='{% url 'helpdesk_rss_queue' queue.slug %}'><img src='{{ STATIC_URL }}helpdesk/rss_icon.png' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "Open Tickets" %}' border='0' /></a></td> <td align='center'><a href='{% url 'helpdesk_rss_queue' queue.slug %}'><img src='{{ STATIC_URL }}helpdesk/rss_icon.png' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "Open Tickets" %}' border='0' /></a></td>
<td align='center'><a href='{% url 'helpdesk_rss_user_queue' user.username queue.slug %}'><img src='{{ STATIC_URL }}helpdesk/rss_icon.png' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "My Open Tickets" %}' border='0' /></a></td> <td align='center'><a href='{% url 'helpdesk_rss_user_queue' user.get_username queue.slug %}'><img src='{{ STATIC_URL }}helpdesk/rss_icon.png' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "My Open Tickets" %}' border='0' /></a></td>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>

View File

@ -1,4 +1,4 @@
{% extends "helpdesk/base.html" %}{% load i18n %}{% load url from future %} {% extends "helpdesk/base.html" %}{% load i18n %}{% load url from future %}{% load user_admin_url %}
{% block helpdesk_title %}{% trans "Change System Settings" %}{% endblock %} {% block helpdesk_title %}{% trans "Change System Settings" %}{% endblock %}
@ -14,6 +14,6 @@
<li><a href='{% url 'admin:helpdesk_kbcategory_changelist' %}'>{% trans "Maintain Knowledgebase Categories" %}</a></li> <li><a href='{% url 'admin:helpdesk_kbcategory_changelist' %}'>{% trans "Maintain Knowledgebase Categories" %}</a></li>
<li><a href='{% url 'admin:helpdesk_kbitem_changelist' %}'>{% trans "Maintain Knowledgebase Items" %}</a></li> <li><a href='{% url 'admin:helpdesk_kbitem_changelist' %}'>{% trans "Maintain Knowledgebase Items" %}</a></li>
<li><a href='{% url 'admin:helpdesk_emailtemplate_changelist' %}'>{% trans "Maintain E-Mail Templates" %}</a></li> <li><a href='{% url 'admin:helpdesk_emailtemplate_changelist' %}'>{% trans "Maintain E-Mail Templates" %}</a></li>
<li><a href='{% url 'admin:auth_user_changelist' %}'>{% trans "Maintain Users" %}</a></li> <li><a href='{% url 'changelist'|user_admin_url %}'>{% trans "Maintain Users" %}</a></li>
</ul> </ul>
{% endblock %} {% endblock %}

View File

@ -4,7 +4,7 @@
<tr class='row_tablehead'><td colspan='2'><h3>{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}]</h3> <span class='ticket_toolbar'> <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"><span class="glyphicon glyphicon-pencil"></span> Edit</a> <a href='{% url 'helpdesk_edit' ticket.id %}' class="ticket-edit"><span class="glyphicon glyphicon-pencil"></span> Edit</a>
| <a href='{% url 'helpdesk_delete' ticket.id %}' class="ticket-delete"><span class="glyphicon glyphicon-remove"></span> Delete</a> | <a href='{% url 'helpdesk_delete' ticket.id %}' class="ticket-delete"><span class="glyphicon glyphicon-remove"></span> Delete</a>
{% if ticket.on_hold %} | <a href='unhold/' class="ticket-hold">{% trans "Unhold" %}</a>{% else %} | <a href='hold/' class="ticket-hold">{% trans "Hold" %}</a>{% endif %} {% if ticket.on_hold %} | <a href='{% url 'helpdesk_unhold' ticket.id %}' class="ticket-hold">{% trans "Unhold" %}</a>{% else %} | <a href='{% url 'helpdesk_hold' ticket.id %}' class="ticket-hold">{% trans "Hold" %}</a>{% endif %}
</span></td></tr> </span></td></tr>
<tr><th colspan='2'>{% blocktrans with ticket.queue as queue %}Queue: {{ queue }}{% endblocktrans %}</th></tr> <tr><th colspan='2'>{% blocktrans with ticket.queue as queue %}Queue: {{ queue }}{% endblocktrans %}</th></tr>
</thead> </thead>

View File

@ -106,7 +106,7 @@ $(document).ready(function() {
<select id='id_owners' name='assigned_to' multiple='selected' size='5'> <select id='id_owners' name='assigned_to' multiple='selected' size='5'>
{% for u in user_choices %} {% for u in user_choices %}
<option value='{{ u.id }}'{% if u.id|in_list:query_params.filtering.assigned_to__id__in %} selected='selected'{% endif %}> <option value='{{ u.id }}'{% if u.id|in_list:query_params.filtering.assigned_to__id__in %} selected='selected'{% endif %}>
{{ u.username }}{% ifequal u user %} {% trans "(ME)" %}{% endifequal %} {{ u.get_username }}{% ifequal u user %} {% trans "(ME)" %}{% endifequal %}
</option> </option>
{% endfor %} {% endfor %}
</select> </select>
@ -199,7 +199,7 @@ $(document).ready(function() {
<form method='get' action='{% url 'helpdesk_list' %}'> <form method='get' action='{% url 'helpdesk_list' %}'>
<p><label for='id_query_selector'>{% trans "Query" %}</label> <select name='saved_query' id='id_query_selector'> <p><label for='id_query_selector'>{% trans "Query" %}</label> <select name='saved_query' id='id_query_selector'>
{% for q in user_saved_queries %} {% for q in user_saved_queries %}
<option value='{{ q.id }}'>{{ q.title }}{% if q.shared %} (Shared{% ifnotequal user q.user %} by {{ q.user.username }}{% endifnotequal %}){% endif %}</option> <option value='{{ q.id }}'>{{ q.title }}{% if q.shared %} (Shared{% ifnotequal user q.user %} by {{ q.user.get_username }}{% endifnotequal %}){% endif %}</option>
{% endfor %} {% endfor %}
</select></p> </select></p>
<input class="btn btn-primary" type='submit' value='{% trans "Run Query" %}'> <input class="btn btn-primary" type='submit' value='{% trans "Run Query" %}'>
@ -257,7 +257,7 @@ $(document).ready(function() {
<p><label>{% trans "Select:" %} </label> <a href='#select_all' id='select_all'>{% trans "All" %}</a> <a href='#select_none' id='select_none'>{% trans "None" %}</a> <a href='#select_inverse' id='select_inverse'>{% trans "Inverse" %}</a></p> <p><label>{% trans "Select:" %} </label> <a href='#select_all' id='select_all'>{% trans "All" %}</a> <a href='#select_none' id='select_none'>{% trans "None" %}</a> <a href='#select_inverse' id='select_inverse'>{% trans "Inverse" %}</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.username }}</option>{% endfor %}</optgroup></select> <input type='submit' value='Go' /></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> <input type='submit' value='Go' /></p>
{% csrf_token %}</form> {% csrf_token %}</form>
{% endblock %} {% endblock %}
</div> </div>

View File

@ -0,0 +1,22 @@
"""
django-helpdesk - A Django powered ticket tracker for small enterprise.
(c) Copyright 2008 Jutda. All Rights Reserved. See LICENSE for details.
templatetags/admin_url.py - Very simple template tag allow linking to the
right auth user model urls.
{% url 'changelist'|user_admin_url %}
"""
from django import template
from django.contrib.auth import get_user_model
def user_admin_url(action):
user = get_user_model()
return 'admin:%s_%s_%s' % (
user._meta.app_label, user._meta.module_name.lower(),
action)
register = template.Library()
register.filter(user_admin_url)

View File

@ -25,7 +25,7 @@ class TestKBDisabled(TestCase):
"""Test proper rendering of navigation.html by accessing the dashboard""" """Test proper rendering of navigation.html by accessing the dashboard"""
from django.core.urlresolvers import NoReverseMatch from django.core.urlresolvers import NoReverseMatch
self.client.login(username=get_staff_user().username, password='password') self.client.login(username=get_staff_user().get_username(), password='password')
self.assertRaises(NoReverseMatch, reverse, 'helpdesk_kb_index') self.assertRaises(NoReverseMatch, reverse, 'helpdesk_kb_index')
try: try:
response = self.client.get(reverse('helpdesk_dashboard')) response = self.client.get(reverse('helpdesk_dashboard'))

View File

@ -5,6 +5,8 @@ from django.test.client import Client
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
class TicketBasicsTestCase(TestCase): class TicketBasicsTestCase(TestCase):
fixtures = ['emailtemplate.json']
def setUp(self): def setUp(self):
self.queue_public = Queue.objects.create(title='Queue 1', slug='q1', allow_public_submission=True, new_ticket_cc='new.public@example.com', updated_ticket_cc='update.public@example.com') self.queue_public = Queue.objects.create(title='Queue 1', slug='q1', allow_public_submission=True, new_ticket_cc='new.public@example.com', updated_ticket_cc='update.public@example.com')
self.queue_private = Queue.objects.create(title='Queue 2', slug='q2', allow_public_submission=False, new_ticket_cc='new.private@example.com', updated_ticket_cc='update.private@example.com') self.queue_private = Queue.objects.create(title='Queue 2', slug='q2', allow_public_submission=False, new_ticket_cc='new.private@example.com', updated_ticket_cc='update.private@example.com')

View File

@ -110,7 +110,7 @@ class API:
def api_public_create_ticket(self): def api_public_create_ticket(self):
form = TicketForm(self.request.POST) form = TicketForm(self.request.POST)
form.fields['queue'].choices = [[q.id, q.title] for q in Queue.objects.all()] form.fields['queue'].choices = [[q.id, q.title] for q in Queue.objects.all()]
form.fields['assigned_to'].choices = [[u.id, u.username] for u in User.objects.filter(is_active=True)] form.fields['assigned_to'].choices = [[u.id, u.get_username()] for u in User.objects.filter(is_active=True)]
if form.is_valid(): if form.is_valid():
ticket = form.save(user=self.request.user) ticket = form.save(user=self.request.user)

View File

@ -38,22 +38,22 @@ class OpenTicketsByUser(Feed):
if obj['queue']: if obj['queue']:
return _("Helpdesk: Open Tickets in queue %(queue)s for %(username)s") % { return _("Helpdesk: Open Tickets in queue %(queue)s for %(username)s") % {
'queue': obj['queue'].title, 'queue': obj['queue'].title,
'username': obj['user'].username, 'username': obj['user'].get_username(),
} }
else: else:
return _("Helpdesk: Open Tickets for %(username)s") % { return _("Helpdesk: Open Tickets for %(username)s") % {
'username': obj['user'].username, 'username': obj['user'].get_username(),
} }
def description(self, obj): def description(self, obj):
if obj['queue']: if obj['queue']:
return _("Open and Reopened Tickets in queue %(queue)s for %(username)s") % { return _("Open and Reopened Tickets in queue %(queue)s for %(username)s") % {
'queue': obj['queue'].title, 'queue': obj['queue'].title,
'username': obj['user'].username, 'username': obj['user'].get_username(),
} }
else: else:
return _("Open and Reopened Tickets for %(username)s") % { return _("Open and Reopened Tickets for %(username)s") % {
'username': obj['user'].username, 'username': obj['user'].get_username(),
} }
def link(self, obj): def link(self, obj):
@ -90,7 +90,7 @@ class OpenTicketsByUser(Feed):
def item_author_name(self, item): def item_author_name(self, item):
if item.assigned_to: if item.assigned_to:
return item.assigned_to.username return item.assigned_to.get_username()
else: else:
return _('Unassigned') return _('Unassigned')
@ -116,7 +116,7 @@ class UnassignedTickets(Feed):
def item_author_name(self, item): def item_author_name(self, item):
if item.assigned_to: if item.assigned_to:
return item.assigned_to.username return item.assigned_to.get_username()
else: else:
return _('Unassigned') return _('Unassigned')
@ -168,7 +168,7 @@ class OpenTicketsByQueue(Feed):
def item_author_name(self, item): def item_author_name(self, item):
if item.assigned_to: if item.assigned_to:
return item.assigned_to.username return item.assigned_to.get_username()
else: else:
return _('Unassigned') return _('Unassigned')

View File

@ -261,7 +261,7 @@ def return_ticketccstring_and_show_subscribe(user, ticket):
''' used in view_ticket() and followup_edit()''' ''' used in view_ticket() and followup_edit()'''
# create the ticketcc_string and check whether current user is already # create the ticketcc_string and check whether current user is already
# subscribed # subscribed
username = user.username.upper() username = user.get_username().upper()
useremail = user.email.upper() useremail = user.email.upper()
strings_to_check = list() strings_to_check = list()
strings_to_check.append(username) strings_to_check.append(username)
@ -364,7 +364,7 @@ def update_ticket(request, ticket_id, public=False):
if owner != 0 and ((ticket.assigned_to and owner != ticket.assigned_to.id) or not ticket.assigned_to): if owner != 0 and ((ticket.assigned_to and owner != ticket.assigned_to.id) or not ticket.assigned_to):
new_user = User.objects.get(id=owner) new_user = User.objects.get(id=owner)
f.title = _('Assigned to %(username)s') % { f.title = _('Assigned to %(username)s') % {
'username': new_user.username, 'username': new_user.get_username(),
} }
ticket.assigned_to = new_user ticket.assigned_to = new_user
reassigned = True reassigned = True
@ -571,7 +571,7 @@ def mass_update(request):
if action == 'assign' and t.assigned_to != user: if action == 'assign' and t.assigned_to != user:
t.assigned_to = user t.assigned_to = user
t.save() t.save()
f = FollowUp(ticket=t, date=timezone.now(), title=_('Assigned to %(username)s in bulk update' % {'username': user.username}), public=True, user=request.user) f = FollowUp(ticket=t, date=timezone.now(), title=_('Assigned to %(username)s in bulk update' % {'username': user.get_username()}), public=True, user=request.user)
f.save() f.save()
elif action == 'unassign' and t.assigned_to is not None: elif action == 'unassign' and t.assigned_to is not None:
t.assigned_to = None t.assigned_to = None
@ -849,14 +849,14 @@ edit_ticket = staff_member_required(edit_ticket)
def create_ticket(request): def create_ticket(request):
if helpdesk_settings.HELPDESK_STAFF_ONLY_TICKET_OWNERS: if helpdesk_settings.HELPDESK_STAFF_ONLY_TICKET_OWNERS:
assignable_users = User.objects.filter(is_active=True, is_staff=True).order_by('username') assignable_users = User.objects.filter(is_active=True, is_staff=True).order_by(User.USERNAME_FIELD)
else: else:
assignable_users = User.objects.filter(is_active=True).order_by('username') assignable_users = User.objects.filter(is_active=True).order_by(User.USERNAME_FIELD)
if request.method == 'POST': if request.method == 'POST':
form = TicketForm(request.POST, request.FILES) form = TicketForm(request.POST, request.FILES)
form.fields['queue'].choices = [('', '--------')] + [[q.id, q.title] for q in Queue.objects.all()] form.fields['queue'].choices = [('', '--------')] + [[q.id, q.title] for q in Queue.objects.all()]
form.fields['assigned_to'].choices = [('', '--------')] + [[u.id, u.username] for u in assignable_users] form.fields['assigned_to'].choices = [('', '--------')] + [[u.id, u.get_username()] for u in assignable_users]
if form.is_valid(): if form.is_valid():
ticket = form.save(user=request.user) ticket = form.save(user=request.user)
return HttpResponseRedirect(ticket.get_absolute_url()) return HttpResponseRedirect(ticket.get_absolute_url())
@ -869,7 +869,7 @@ def create_ticket(request):
form = TicketForm(initial=initial_data) form = TicketForm(initial=initial_data)
form.fields['queue'].choices = [('', '--------')] + [[q.id, q.title] for q in Queue.objects.all()] form.fields['queue'].choices = [('', '--------')] + [[q.id, q.title] for q in Queue.objects.all()]
form.fields['assigned_to'].choices = [('', '--------')] + [[u.id, u.username] for u in assignable_users] form.fields['assigned_to'].choices = [('', '--------')] + [[u.id, u.get_username()] for u in assignable_users]
if helpdesk_settings.HELPDESK_CREATE_TICKET_HIDE_ASSIGNED_TO: if helpdesk_settings.HELPDESK_CREATE_TICKET_HIDE_ASSIGNED_TO:
form.fields['assigned_to'].widget = forms.HiddenInput() form.fields['assigned_to'].widget = forms.HiddenInput()

View File

@ -1,8 +1,11 @@
import os import os
import sys import sys
import argparse import argparse
import django
from django.conf import settings from django.conf import settings
class QuickDjangoTest(object): class QuickDjangoTest(object):
""" """
A quick way to run the Django test suite without a fully-configured project. A quick way to run the Django test suite without a fully-configured project.
@ -25,6 +28,14 @@ class QuickDjangoTest(object):
'django.contrib.humanize', 'django.contrib.humanize',
'bootstrapform', 'bootstrapform',
) )
MIDDLEWARE_CLASSES = [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.apps = args self.apps = args
@ -40,8 +51,7 @@ class QuickDjangoTest(object):
""" """
Figure out which version of Django's test suite we have to play with. Figure out which version of Django's test suite we have to play with.
""" """
from django import VERSION if django.VERSION >= (1, 2):
if VERSION[0] == 1 and VERSION[1] >= 2:
return 'new' return 'new'
else: else:
return 'old' return 'old'
@ -64,6 +74,7 @@ class QuickDjangoTest(object):
""" """
Fire up the Django test suite developed for version 1.2 Fire up the Django test suite developed for version 1.2
""" """
settings.configure( settings.configure(
DEBUG = True, DEBUG = True,
DATABASES = { DATABASES = {
@ -77,8 +88,13 @@ class QuickDjangoTest(object):
} }
}, },
INSTALLED_APPS = self.INSTALLED_APPS + self.apps, INSTALLED_APPS = self.INSTALLED_APPS + self.apps,
MIDDLEWARE_CLASSES = self.MIDDLEWARE_CLASSES,
ROOT_URLCONF = self.apps[0] + '.urls', ROOT_URLCONF = self.apps[0] + '.urls',
) )
if django.VERSION >= (1, 7):
django.setup()
from django.test.simple import DjangoTestSuiteRunner from django.test.simple import DjangoTestSuiteRunner
failures = DjangoTestSuiteRunner().run_tests(self.apps, verbosity=1) failures = DjangoTestSuiteRunner().run_tests(self.apps, verbosity=1)
if failures: if failures:

View File

@ -4,7 +4,7 @@ from distutils.util import convert_path
from fnmatch import fnmatchcase from fnmatch import fnmatchcase
from setuptools import setup, find_packages from setuptools import setup, find_packages
version = '0.1.12' version = '0.1.16'
# Provided as an attribute, so you can append to these instead # Provided as an attribute, so you can append to these instead
# of replicating them: # of replicating them: