* 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:
Ross Poulton 2008-09-09 08:32:01 +00:00
parent a162d77d70
commit 3f8fc2cd68
10 changed files with 173 additions and 34 deletions

4
README
View File

@ -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
#########################

View File

@ -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,
)

View File

@ -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__':

View File

@ -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)

View File

@ -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" %}

View 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 %}

View File

@ -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'),
)

View File

@ -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,

View File

@ -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)

View File

@ -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)