diff --git a/MANIFEST.in b/MANIFEST.in index c62f8817..d277e6cb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,7 @@ include README include UPGRADE include LICENSE* include CHANGELOG +include requirements.txt recursive-include helpdesk/static/helpdesk * recursive-include helpdesk/locale *.po *.mo diff --git a/docs/configuration.rst b/docs/configuration.rst index 0ab6d6f5..d9a67b29 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -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 - 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:: @@ -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_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. diff --git a/docs/install.rst b/docs/install.rst index f9561984..f386ea38 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -29,7 +29,7 @@ Download, extract, and drop ``helpdesk`` into your ``PYTHONPATH`` 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 = ( 'django.contrib.auth', @@ -37,11 +37,11 @@ Adding To Your Django Project 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.admin', # Required for helpdesk admin/maintenance - 'django.contrib.markup', # Required for helpdesk text display '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 - '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``:: @@ -58,7 +58,7 @@ Adding To Your Django Project ./manage.py syncdb - Then migrate using South + Then migrate using South / Django 1.7+ migrations:: ./manage.py migrate helpdesk @@ -98,3 +98,7 @@ Adding To Your Django Project LOGIN_URL = '/helpdesk/login/' 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 diff --git a/helpdesk/fixtures/emailtemplates.json b/helpdesk/fixtures/emailtemplate.json similarity index 100% rename from helpdesk/fixtures/emailtemplates.json rename to helpdesk/fixtures/emailtemplate.json diff --git a/helpdesk/forms.py b/helpdesk/forms.py index eb0dde3f..23f08424 100644 --- a/helpdesk/forms.py +++ b/helpdesk/forms.py @@ -102,7 +102,7 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm): for field, value in self.cleaned_data.items(): if field.startswith('custom_'): - field_name = field.replace('custom_', '') + field_name = field.replace('custom_', '', 1) customfield = CustomField.objects.get(name=field_name) try: 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(): if field.startswith('custom_'): - field_name = field.replace('custom_', '') + field_name = field.replace('custom_', '', 1) customfield = CustomField.objects.get(name=field_name) cfv = TicketCustomFieldValue(ticket=t, field=customfield, @@ -369,6 +369,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form): required=False, label=_('Attach File'), help_text=_('You can attach a file such as a document or screenshot to this ticket.'), + max_length=1000, ) def __init__(self, *args, **kwargs): @@ -407,7 +408,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form): for field, value in self.cleaned_data.items(): if field.startswith('custom_'): - field_name = field.replace('custom_', '') + field_name = field.replace('custom_', '', 1) customfield = CustomField.objects.get(name=field_name) cfv = TicketCustomFieldValue(ticket=t, field=customfield, @@ -529,9 +530,9 @@ class TicketCCForm(forms.ModelForm): 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('username') + 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('username') + users = User.objects.filter(is_active=True).order_by(User.USERNAME_FIELD) self.fields['user'].queryset = users class Meta: model = TicketCC diff --git a/helpdesk/lib.py b/helpdesk/lib.py index 5d90c8cb..22ef1a44 100644 --- a/helpdesk/lib.py +++ b/helpdesk/lib.py @@ -115,7 +115,7 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b elif type(recipients) != list: recipients = [recipients,] - msg = EmailMultiAlternatives( subject_part.replace('\n', ''), + msg = EmailMultiAlternatives( subject_part.replace('\n', '').replace('\r', ''), text_part, sender, recipients, diff --git a/helpdesk/management/commands/get_email.py b/helpdesk/management/commands/get_email.py index 8d4fede6..e6a3b124 100644 --- a/helpdesk/management/commands/get_email.py +++ b/helpdesk/management/commands/get_email.py @@ -15,6 +15,7 @@ import imaplib import mimetypes import poplib import re +import socket from datetime import timedelta from email.header import decode_header @@ -84,6 +85,22 @@ def process_queue(q, quiet=False): if not quiet: 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 if email_box_type == 'pop3': diff --git a/helpdesk/migrations/0002_socks_proxy.py b/helpdesk/migrations/0002_socks_proxy.py new file mode 100644 index 00000000..468af823 --- /dev/null +++ b/helpdesk/migrations/0002_socks_proxy.py @@ -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, + ), + ] diff --git a/helpdesk/migrations/0003_populate_usersettings.py b/helpdesk/migrations/0003_populate_usersettings.py new file mode 100644 index 00000000..ebf57a8b --- /dev/null +++ b/helpdesk/migrations/0003_populate_usersettings.py @@ -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), + ] + + diff --git a/helpdesk/migrations/0004_initial_data_import.py b/helpdesk/migrations/0004_initial_data_import.py new file mode 100644 index 00000000..e64f4112 --- /dev/null +++ b/helpdesk/migrations/0004_initial_data_import.py @@ -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), + ] diff --git a/helpdesk/models.py b/helpdesk/models.py index 453b337d..505b3cba 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -11,6 +11,7 @@ from django.db import models from django.contrib.auth import get_user_model from django.conf import settings from django.utils.translation import ugettext_lazy as _, ugettext +from django import VERSION try: from django.utils import timezone @@ -178,6 +179,29 @@ class Queue(models.Model): # 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): 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: 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 self.email_box_type == 'imap' and self.email_box_ssl: self.email_box_port = 993 @@ -351,7 +384,7 @@ class Ticket(models.Model): if self.assigned_to.get_full_name(): return self.assigned_to.get_full_name() else: - return self.assigned_to.username + return self.assigned_to.get_username() get_assigned_to = property(_get_assigned_to) def _get_ticket(self): @@ -995,7 +1028,7 @@ class UserSettings(models.Model): 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 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. """ from helpdesk.settings import DEFAULT_USER_SETTINGS - if sender == settings.AUTH_USER_MODEL and created: - # This is a new user, so lets create their settings entry. - 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() + if created: + UserSettings.objects.create(user=instance, settings=DEFAULT_USER_SETTINGS) -models.signals.post_syncdb.connect(create_usersettings) 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) except: signal_user = get_user_model() models.signals.post_save.connect(create_usersettings, sender=signal_user) + class IgnoreEmail(models.Model): """ This model lets us easily ignore e-mails from certain senders when diff --git a/helpdesk/south_migrations/0010_auto__add_field_queue_socks_proxy_type__add_field_queue_socks_proxy_ho.py b/helpdesk/south_migrations/0010_auto__add_field_queue_socks_proxy_type__add_field_queue_socks_proxy_ho.py new file mode 100644 index 00000000..62317cc7 --- /dev/null +++ b/helpdesk/south_migrations/0010_auto__add_field_queue_socks_proxy_type__add_field_queue_socks_proxy_ho.py @@ -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'] \ No newline at end of file diff --git a/helpdesk/south_migrations/0011_populate_usersettings.py b/helpdesk/south_migrations/0011_populate_usersettings.py new file mode 100644 index 00000000..8b0514b2 --- /dev/null +++ b/helpdesk/south_migrations/0011_populate_usersettings.py @@ -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 diff --git a/helpdesk/templates/helpdesk/base.html b/helpdesk/templates/helpdesk/base.html index 9ef38d9c..71ad129e 100644 --- a/helpdesk/templates/helpdesk/base.html +++ b/helpdesk/templates/helpdesk/base.html @@ -17,7 +17,7 @@ - + diff --git a/helpdesk/templates/helpdesk/navigation.html b/helpdesk/templates/helpdesk/navigation.html index 59bc3f0d..ff3fae31 100644 --- a/helpdesk/templates/helpdesk/navigation.html +++ b/helpdesk/templates/helpdesk/navigation.html @@ -26,13 +26,13 @@ {% for q in user_saved_queries_ %}