forked from extern/django-helpdesk
* Added force_insert and force_update parameters to model save() overrides (as per Django rev 8670)
* Added 'UserSettings' model to provide a user profile system independent of existing Django user profiles, for two reasons: 1) Avoids users having to update settings.py and 2) Allows jutda-helpdesk to integrate with websites who already use a User Profile * Settings added in this revision allow a user to control e-mail alerts, and to determine whether they see the dashboard or ticket list at login. * New 'Settings' link in page footer for signed-in users * Logout now takes you to the Helpdesk homepage * Fixed file attachment bug in management/commands/get_email.py which seemed to have been un-done (fixes issue # 4. Jutda-helpdesk is now compatible with Django 1.0!
This commit is contained in:
parent
a162d77d70
commit
3f8fc2cd68
4
README
4
README
@ -31,10 +31,6 @@ LICENSE.JQUERY and LICENSE.NICEDIT for their respective license terms.
|
||||
3. An existing WORKING Django project with database etc. If you
|
||||
cannot log into the Admin, you won't get this product working.
|
||||
|
||||
4. pygooglechart (needs minor mods to @staticmethod calls for python 2.3)
|
||||
http://pygooglechart.slowchop.com/
|
||||
|
||||
|
||||
#########################
|
||||
3. Installation
|
||||
#########################
|
||||
|
27
forms.py
27
forms.py
@ -112,7 +112,7 @@ class TicketForm(forms.Form):
|
||||
fail_silently=True,
|
||||
)
|
||||
|
||||
if t.assigned_to and t.assigned_to != user:
|
||||
if t.assigned_to and t.assigned_to != user and getattr(t.assigned_to.usersettings.settings, 'email_on_ticket_assign', False):
|
||||
send_templated_mail(
|
||||
'assigned_owner',
|
||||
context,
|
||||
@ -240,3 +240,28 @@ class PublicTicketForm(forms.Form):
|
||||
|
||||
return t
|
||||
|
||||
|
||||
class UserSettingsForm(forms.Form):
|
||||
login_view_ticketlist = forms.BooleanField(
|
||||
label=_('Show Ticket List on Login?'),
|
||||
help_text=_('Display the ticket list upon login? Otherwise, the dashboard is shown.'),
|
||||
required=False,
|
||||
)
|
||||
|
||||
email_on_ticket_change = forms.BooleanField(
|
||||
label=_('E-mail me on ticket change?'),
|
||||
help_text=_('If you\'re the ticket owner and the ticket is changed via the web by somebody else, do you want to receive an e-mail?'),
|
||||
required=False,
|
||||
)
|
||||
|
||||
email_on_ticket_assign = forms.BooleanField(
|
||||
label=_('E-mail me when assigned a ticket?'),
|
||||
help_text=_('If you are assigned a ticket via the web, do you want to receive an e-mail?'),
|
||||
required=False,
|
||||
)
|
||||
|
||||
email_on_ticket_apichange = forms.BooleanField(
|
||||
label=_('E-mail me when a ticket is changed via the API?'),
|
||||
help_text=_('If a ticket is altered by the API, do you want to receive an e-mail?'),
|
||||
required=False,
|
||||
)
|
||||
|
@ -245,15 +245,16 @@ def ticket_from_message(message, queue):
|
||||
|
||||
for file in files:
|
||||
filename = file['filename'].replace(' ', '_')
|
||||
a = Attachment(
|
||||
followup=f,
|
||||
filename=filename,
|
||||
mime_type=file['type'],
|
||||
size=len(file['content']),
|
||||
)
|
||||
a.file.save(file['filename'], ContentFile(file['content']))
|
||||
a.save()
|
||||
print " - %s" % file['filename']
|
||||
if file['content']:
|
||||
a = Attachment(
|
||||
followup=f,
|
||||
filename=filename,
|
||||
mime_type=file['type'],
|
||||
size=len(file['content']),
|
||||
)
|
||||
a.file.save(file['filename'], ContentFile(file['content']))
|
||||
a.save()
|
||||
print " - %s" % file['filename']
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
86
models.py
86
models.py
@ -182,7 +182,7 @@ class Queue(models.Model):
|
||||
return u'%s <%s>' % (self.title, self.email_address)
|
||||
from_address = property(_from_address)
|
||||
|
||||
def save(self):
|
||||
def save(self, force_insert=False, force_update=False):
|
||||
if self.email_box_type == 'imap' and not self.email_box_imap_folder:
|
||||
self.email_box_imap_folder = 'INBOX'
|
||||
|
||||
@ -195,7 +195,7 @@ class Queue(models.Model):
|
||||
self.email_box_port = 995
|
||||
elif self.email_box_type == 'pop3' and not self.email_box_ssl:
|
||||
self.email_box_port = 110
|
||||
super(Queue, self).save()
|
||||
super(Queue, self).save(force_insert, force_update)
|
||||
|
||||
|
||||
class Ticket(models.Model):
|
||||
@ -402,7 +402,7 @@ class Ticket(models.Model):
|
||||
return ('helpdesk_view', [str(self.id)])
|
||||
get_absolute_url = models.permalink(get_absolute_url)
|
||||
|
||||
def save(self):
|
||||
def save(self, force_insert=False, force_update=False):
|
||||
if not self.id:
|
||||
# This is a new ticket as no ID yet exists.
|
||||
self.created = datetime.now()
|
||||
@ -412,7 +412,7 @@ class Ticket(models.Model):
|
||||
|
||||
self.modified = datetime.now()
|
||||
|
||||
super(Ticket, self).save()
|
||||
super(Ticket, self).save(force_insert, force_update)
|
||||
|
||||
|
||||
class FollowUpManager(models.Manager):
|
||||
@ -488,12 +488,12 @@ class FollowUp(models.Model):
|
||||
def get_absolute_url(self):
|
||||
return u"%s#followup%s" % (self.ticket.get_absolute_url(), self.id)
|
||||
|
||||
def save(self):
|
||||
def save(self, force_insert=False, force_update=False):
|
||||
t = self.ticket
|
||||
t.modified = datetime.now()
|
||||
self.date = datetime.now()
|
||||
t.save()
|
||||
super(FollowUp, self).save()
|
||||
super(FollowUp, self).save(force_insert, force_update)
|
||||
|
||||
|
||||
class TicketChange(models.Model):
|
||||
@ -796,9 +796,9 @@ class KBItem(models.Model):
|
||||
'changed.'),
|
||||
)
|
||||
|
||||
def save(self):
|
||||
def save(self, force_insert=False, force_update=False):
|
||||
self.last_updated = datetime.now()
|
||||
return super(KBItem, self).save()
|
||||
return super(KBItem, self).save(force_insert, force_update)
|
||||
|
||||
def _score(self):
|
||||
if self.votes > 0:
|
||||
@ -855,3 +855,73 @@ class SavedSearch(models.Model):
|
||||
return u'%s (*)' % self.title
|
||||
else:
|
||||
return u'%s' % self.title
|
||||
|
||||
class UserSettings(models.Model):
|
||||
"""
|
||||
A bunch of user-specific settings that we want to be able to define, such
|
||||
as notification preferences and other things that should probably be
|
||||
configurable.
|
||||
|
||||
We should always refer to user.usersettings.settings['setting_name'].
|
||||
"""
|
||||
|
||||
user = models.OneToOneField(User)
|
||||
|
||||
settings_pickled = models.TextField(
|
||||
_('Settings Dictionary'),
|
||||
help_text=_('This is a base64-encoded representation of a pickled Python dictionary. Do not change this field via the admin.'),
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
def _set_settings(self, data):
|
||||
# data should always be a Python dictionary.
|
||||
import cPickle, base64
|
||||
self.settings_pickled = base64.urlsafe_b64encode(cPickle.dumps(data))
|
||||
|
||||
def _get_settings(self):
|
||||
# return a python dictionary representing the pickled data.
|
||||
import cPickle, base64
|
||||
try:
|
||||
return cPickle.loads(base64.urlsafe_b64decode(str(self.settings_pickled)))
|
||||
except cPickle.UnpicklingError:
|
||||
return {}
|
||||
|
||||
settings = property(_get_settings, _set_settings)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'Preferences for %s' % self.user
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'User Settings'
|
||||
verbose_name_plural = 'User Settings'
|
||||
|
||||
|
||||
def create_usersettings(sender, created_models=[], instance=None, created=False, **kwargs):
|
||||
"""
|
||||
Helper function to create UserSettings instances as
|
||||
required, eg when we first create the UserSettings database
|
||||
table via 'syncdb' or when we save a new user.
|
||||
|
||||
If we end up with users with no UserSettings, then we get horrible
|
||||
'DoesNotExist: UserSettings matching query does not exist.' errors.
|
||||
"""
|
||||
if sender == User and created:
|
||||
# This is a new user, so lets create their settings entry.
|
||||
s = UserSettings(user=instance)
|
||||
s.save()
|
||||
elif UserSettings in created_models:
|
||||
# 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)
|
||||
s.save()
|
||||
|
||||
models.signals.post_syncdb.connect(create_usersettings)
|
||||
models.signals.post_save.connect(create_usersettings, sender=User)
|
||||
|
||||
|
@ -28,7 +28,7 @@
|
||||
{% block helpdesk_body %}{% endblock %}
|
||||
</div>
|
||||
<div id='footer'>
|
||||
<p>{% trans "Powered by <a href='http://www.jutda.com.au/'>Jutda Helpdesk</a>." %} <a href='{% url helpdesk_rss_index %}'><img src='{{ MEDIA_URL }}/helpdesk/rss_icon.png' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "RSS Feeds" %}' border='0' />{% trans "RSS Feeds" %}</a> <a href='{% url helpdesk_api_help %}'>{% trans "API" %}</a></p>
|
||||
<p>{% trans "Powered by <a href='http://www.jutda.com.au/'>Jutda Helpdesk</a>." %} <a href='{% url helpdesk_rss_index %}'><img src='{{ MEDIA_URL }}/helpdesk/rss_icon.png' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "RSS Feeds" %}' border='0' />{% trans "RSS Feeds" %}</a> <a href='{% url helpdesk_api_help %}'>{% trans "API" %}</a> <a href='{% url helpdesk_user_settings %}'>{% trans "User Settings" %}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
{% include "helpdesk/debug.html" %}
|
||||
|
22
templates/helpdesk/user_settings.html
Normal file
22
templates/helpdesk/user_settings.html
Normal file
@ -0,0 +1,22 @@
|
||||
{% extends "helpdesk/base.html" %}{% load i18n %}
|
||||
|
||||
{% block helpdesk_title %}{% trans "Change User Settings" %}{% endblock %}
|
||||
|
||||
{% block helpdesk_body %}{% blocktrans %}
|
||||
<h2>User Settings</h2>
|
||||
|
||||
<p>Use the following options to change the way your helpdesk system works for you. These settings do not impact any other user.</p>{% endblocktrans %}
|
||||
|
||||
<form method='post' action='./'>
|
||||
<fieldset>
|
||||
<dl>{% for field in form %}
|
||||
<dt><label for='id_{{ field.name }}'>{{ field.label }}</label></dt>
|
||||
<dd>{{ field }}</dd>
|
||||
{% if field.errors %}<dd class='error'>{{ field.errors }}</dd>{% endif %}
|
||||
{% if field.help_text %}<dd class='form_help_text'>{{ field.help_text }}</dd>{% endif %}
|
||||
{% endfor %}</dl>
|
||||
</fieldset>
|
||||
<input type='submit' value='{% trans "Save Options" %}' />
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
5
urls.py
5
urls.py
@ -69,6 +69,10 @@ urlpatterns = patterns('helpdesk.views.staff',
|
||||
url(r'^delete_query/(?P<id>[0-9]+)/$',
|
||||
'delete_saved_query',
|
||||
name='helpdesk_delete_query'),
|
||||
|
||||
url(r'^settings/$',
|
||||
'user_settings',
|
||||
name='helpdesk_user_settings'),
|
||||
)
|
||||
|
||||
urlpatterns += patterns('helpdesk.views.public',
|
||||
@ -97,6 +101,7 @@ urlpatterns += patterns('',
|
||||
|
||||
url(r'^logout/$',
|
||||
'django.contrib.auth.views.logout',
|
||||
{'next_page': '../'},
|
||||
name='logout'),
|
||||
)
|
||||
|
||||
|
@ -215,7 +215,7 @@ class API:
|
||||
fail_silently=True,
|
||||
)
|
||||
|
||||
if ticket.assigned_to and self.request.user != ticket.assigned_to:
|
||||
if ticket.assigned_to and self.request.user != ticket.assigned_to and getattr(ticket.assigned_to.usersettings.settings, 'email_on_ticket_apichange', False):
|
||||
send_templated_mail(
|
||||
'updated_owner',
|
||||
context,
|
||||
@ -276,7 +276,7 @@ class API:
|
||||
fail_silently=True,
|
||||
)
|
||||
|
||||
if ticket.assigned_to and self.request.user != ticket.assigned_to:
|
||||
if ticket.assigned_to and self.request.user != ticket.assigned_to and getattr(ticket.assigned_to.usersettings.settings, 'email_on_ticket_apichange', False):
|
||||
send_templated_mail(
|
||||
'resolved_resolved',
|
||||
context,
|
||||
|
@ -22,7 +22,10 @@ from helpdesk.models import Ticket, Queue
|
||||
|
||||
def homepage(request):
|
||||
if request.user.is_authenticated():
|
||||
return HttpResponseRedirect(reverse('helpdesk_dashboard'))
|
||||
if getattr(request.user.usersettings.settings, 'login_view_ticketlist', False):
|
||||
return HttpResponseRedirect(reverse('helpdesk_list'))
|
||||
else:
|
||||
return HttpResponseRedirect(reverse('helpdesk_dashboard'))
|
||||
|
||||
if request.method == 'POST':
|
||||
form = PublicTicketForm(request.POST)
|
||||
|
@ -20,7 +20,7 @@ from django.shortcuts import render_to_response, get_object_or_404
|
||||
from django.template import loader, Context, RequestContext
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from helpdesk.forms import TicketForm
|
||||
from helpdesk.forms import TicketForm, UserSettingsForm
|
||||
from helpdesk.lib import send_templated_mail, line_chart, bar_chart, query_to_dict, apply_query, safe_template_context
|
||||
from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment, SavedSearch
|
||||
|
||||
@ -234,13 +234,14 @@ def update_ticket(request, ticket_id):
|
||||
else:
|
||||
template_staff = 'updated_owner'
|
||||
|
||||
send_templated_mail(
|
||||
template_staff,
|
||||
context,
|
||||
recipients=ticket.assigned_to.email,
|
||||
sender=ticket.queue.from_address,
|
||||
fail_silently=True,
|
||||
)
|
||||
if (not reassigned or ( reassigned and getattr(ticket.assigned_to.usersettings.settings, 'email_on_ticket_assign', False))) or (not reassigned and getattr(ticket.assigned_to.usersettings.settings, 'email_on_ticket_change', False)):
|
||||
send_templated_mail(
|
||||
template_staff,
|
||||
context,
|
||||
recipients=ticket.assigned_to.email,
|
||||
sender=ticket.queue.from_address,
|
||||
fail_silently=True,
|
||||
)
|
||||
|
||||
if ticket.queue.updated_ticket_cc:
|
||||
if reassigned:
|
||||
@ -636,3 +637,19 @@ def delete_saved_query(request, id):
|
||||
'query': query,
|
||||
}))
|
||||
delete_saved_query = login_required(delete_saved_query)
|
||||
|
||||
def user_settings(request):
|
||||
s = request.user.usersettings
|
||||
if request.POST:
|
||||
form = UserSettingsForm(request.POST)
|
||||
if form.is_valid():
|
||||
s.settings = form.cleaned_data
|
||||
s.save()
|
||||
else:
|
||||
form = UserSettingsForm(s.settings)
|
||||
|
||||
return render_to_response('helpdesk/user_settings.html',
|
||||
RequestContext(request, {
|
||||
'form': form,
|
||||
}))
|
||||
user_settings = login_required(user_settings)
|
||||
|
Loading…
Reference in New Issue
Block a user