diff --git a/forms.py b/forms.py index 3d9b30b3..e0ef0a4a 100644 --- a/forms.py +++ b/forms.py @@ -102,3 +102,61 @@ class TicketForm(forms.Form): send_multipart_mail('helpdesk/emails/submitter_newticket', context, '%s %s' % (t.ticket, t.title), t.submitter_email, q.from_address) return t + +class PublicTicketForm(forms.Form): + queue = forms.ChoiceField(label=u'Queue', required=True, choices=()) + + title = forms.CharField(max_length=100, required=True, + widget=forms.TextInput(), + label=u'Summary of your query') + + submitter_email = forms.EmailField(required=True, + label=u'Your E-Mail Address', + help_text=u'We will e-mail you when your ticket is updated.') + + body = forms.CharField(widget=forms.Textarea(), + label=u'Description of your issue', required=True, + help_text=u'Please be as descriptive as possible, including any details we may need to address your query.') + + priority = forms.ChoiceField(choices=Ticket.PRIORITY_CHOICES, + required=True, + initial='3', + label=u'Urgency', + help_text=u'Please select a priority carefully.') + + def save(self): + """ + Writes and returns a Ticket() object + + """ + q = Queue.objects.get(id=int(self.cleaned_data['queue'])) + + t = Ticket( title = self.cleaned_data['title'], + submitter_email = self.cleaned_data['submitter_email'], + created = datetime.now(), + status = Ticket.OPEN_STATUS, + queue = q, + description = self.cleaned_data['body'], + priority = self.cleaned_data['priority'], + ) + + t.save() + + f = FollowUp( ticket = t, + title = 'Ticket Opened Via Web', + date = datetime.now(), + public = True, + comment = self.cleaned_data['body'], + ) + + f.save() + + context = { + 'ticket': t, + 'queue': q, + } + + from helpdesk.lib import send_multipart_mail + send_multipart_mail('helpdesk/emails/submitter_newticket', context, '%s %s' % (t.ticket, t.title), t.submitter_email, q.from_address) + + return t diff --git a/htdocs/helpdesk.css b/htdocs/helpdesk.css index 3e95f772..0ac348df 100644 --- a/htdocs/helpdesk.css +++ b/htdocs/helpdesk.css @@ -47,6 +47,11 @@ dd.form_help_text { font-size: 95%; } +ul.errorlist { + color: #a33; + font-size: 95%; +} + dt { padding-top: 8px; } diff --git a/models.py b/models.py index c3b3798e..73045831 100644 --- a/models.py +++ b/models.py @@ -145,14 +145,25 @@ class Ticket(models.Model): """ A user-friendly ticket ID, which is a combination of ticket ID and queue slug. This is generally used in e-mails. """ - return "[%s-%s]" % (self.queue.slug, self.id) + return "[%s]" % (self.ticket_for_url) ticket = property(_get_ticket) + def _get_ticket_for_url(self): + return "%s-%s" % (self.queue.slug, self.id) + ticket_for_url = property(_get_ticket_for_url) + def _get_priority_img(self): from django.conf import settings return "%s/helpdesk/priorities/priority%s.png" % (settings.MEDIA_URL, self.priority) get_priority_img = property(_get_priority_img) + def _get_ticket_url(self): + from django.contrib.sites.models import Site + from django.core.urlresolvers import reverse + site = Site.objects.get_current() + return "http://%s%s?ticket=%s&email=%s" % (site.domain, reverse('helpdesk_public_view'), self.ticket_for_url, self.submitter_email) + ticket_url = property(_get_ticket_url) + class Admin: list_display = ('title', 'status', 'assigned_to', 'submitter_email',) date_hierarchy = 'created' @@ -180,6 +191,12 @@ class Ticket(models.Model): super(Ticket, self).save() +class FollowUpManager(models.Manager): + def private_followups(self): + return self.filter(public=False) + + def public_followups(self): + return self.filter(public=True) class FollowUp(models.Model): """ A FollowUp is a comment and/or change to a ticket. We keep a simple @@ -197,10 +214,12 @@ class FollowUp(models.Model): title = models.CharField(maxlength=200, blank=True, null=True) comment = models.TextField(blank=True, null=True) public = models.BooleanField(blank=True, null=True) - user = models.ForeignKey(User) + user = models.ForeignKey(User, blank=True, null=True) new_status = models.IntegerField(choices=Ticket.STATUS_CHOICES, blank=True, null=True) + objects = FollowUpManager() + class Meta: ordering = ['date'] diff --git a/templates/helpdesk/base.html b/templates/helpdesk/base.html index b035173d..fc8f93c5 100644 --- a/templates/helpdesk/base.html +++ b/templates/helpdesk/base.html @@ -13,6 +13,7 @@
  • Dashboard
  • Tickets
  • Submit Ticket
  • +
  • Logout
  • {% if not query %}
  • {% endif %} diff --git a/templates/helpdesk/emails/submitter_newticket.html b/templates/helpdesk/emails/submitter_newticket.html index 6d6bbd4e..16738675 100644 --- a/templates/helpdesk/emails/submitter_newticket.html +++ b/templates/helpdesk/emails/submitter_newticket.html @@ -12,6 +12,8 @@

    If you wish to send us further details, or if you have any queries about this ticket, please include the ticket id of {{ ticket.ticket }} in the subject. The easiest way to do this is just press 'reply' to this message.

    +

    If you wish to view this ticket online, you can visit {{ ticket.get_ticket_url }}.

    +

    We will investigate your query and attempt to resolve it as soon as possible. You will receive further updates and a resolution via this e-mail address.

    {% endblock %} diff --git a/templates/helpdesk/emails/submitter_newticket.txt b/templates/helpdesk/emails/submitter_newticket.txt index e2ff4b0a..7045a513 100644 --- a/templates/helpdesk/emails/submitter_newticket.txt +++ b/templates/helpdesk/emails/submitter_newticket.txt @@ -6,6 +6,8 @@ You do not have to do anything further at this stage. Your ticket has been assig If you wish to send us further details, or if you have any queries about this ticket, please include the ticket id of {{ ticket.ticket }} in the subject. The easiest way to do this is just press 'reply' to this message. +If you wish to view this ticket online, you can visit {{ ticket.get_ticket_url }}. + We will investigate your query and attempt to resolve it as soon as possible. You will receive further updates and a resolution via this e-mail address. {% include "helpdesk/emails/text_footer.txt" %} diff --git a/templates/helpdesk/emails/submitter_resolved.html b/templates/helpdesk/emails/submitter_resolved.html index 4cbf1a8f..6d108d0a 100644 --- a/templates/helpdesk/emails/submitter_resolved.html +++ b/templates/helpdesk/emails/submitter_resolved.html @@ -13,4 +13,6 @@

    Can you please confirm that this resolution addresses your needs so we may close this ticket? If you have any further queries, or if you do not believe this resolution is adequate, please reply to this e-mail and keep the subject intact.

    +

    If you wish to view this ticket online, you can visit {{ ticket.get_ticket_url }}.

    + {% endblock %} diff --git a/templates/helpdesk/emails/submitter_resolved.txt b/templates/helpdesk/emails/submitter_resolved.txt index 96d5d9bc..f0adf404 100644 --- a/templates/helpdesk/emails/submitter_resolved.txt +++ b/templates/helpdesk/emails/submitter_resolved.txt @@ -8,4 +8,6 @@ The following resolution was added to ticket {{ ticket.ticket }}: Can you please confirm that this resolution addresses your needs so we may close this ticket? If you have any further queries, or if you do not believe this resolution is adequate, please reply to this e-mail and keep the subject intact. +If you wish to view this ticket online, you can visit {{ ticket.get_ticket_url }}. + {% include "helpdesk/emails/text_footer.txt" %} diff --git a/templates/helpdesk/emails/submitter_updated.html b/templates/helpdesk/emails/submitter_updated.html index a48a6233..44a20c04 100644 --- a/templates/helpdesk/emails/submitter_updated.html +++ b/templates/helpdesk/emails/submitter_updated.html @@ -13,4 +13,6 @@

    To provide us with further information, please reply to this e-mail.

    +

    If you wish to view this ticket online, you can visit {{ ticket.get_ticket_url }}.

    + {% endblock %} diff --git a/templates/helpdesk/emails/submitter_updated.txt b/templates/helpdesk/emails/submitter_updated.txt index 40e1bf44..04a021c1 100644 --- a/templates/helpdesk/emails/submitter_updated.txt +++ b/templates/helpdesk/emails/submitter_updated.txt @@ -8,4 +8,6 @@ The following comment was added to ticket {{ ticket.ticket }}: To provide us with further information, please reply to this e-mail. +If you wish to view this ticket online, you can visit {{ ticket.get_ticket_url }}. + {% include "helpdesk/emails/text_footer.txt" %} diff --git a/templates/helpdesk/public_base.html b/templates/helpdesk/public_base.html new file mode 100644 index 00000000..9f14de12 --- /dev/null +++ b/templates/helpdesk/public_base.html @@ -0,0 +1,25 @@ + + +{% block helpdesk_title %}Helpdesk{% endblock %} + + +{% block helpdesk_head %}{% endblock %} + + +
    + +
    +{% block helpdesk_body %}{% endblock %} +
    + +
    {% include "helpdesk/debug.html" %} + + diff --git a/templates/helpdesk/public_homepage.html b/templates/helpdesk/public_homepage.html new file mode 100644 index 00000000..224ec78a --- /dev/null +++ b/templates/helpdesk/public_homepage.html @@ -0,0 +1,62 @@ +{% extends "helpdesk/public_base.html" %} +{% block helpdesk_title %}Helpdesk{% endblock %} + +{% block helpdesk_body %} +

    View a Ticket

    + +
    +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    + +

    Submit a Ticket

    + +

    All fields are required. Please provide as descriptive a title and description as possible.

    + +
    +
    +
    +
    +
    {{ form.queue }}
    + {% if form.queue.errors %} +
    {{ form.queue.errors }}
    {% endif %} + +
    +
    {{ form.title }}
    + {% if form.title.errors %} +
    {{ form.title.errors }}
    {% endif %} + +
    +
    {{ form.submitter_email }}
    + {% if form.submitter_email.errors %} +
    {{ form.submitter_email.errors }}
    {% endif %} +
    {{ form.submitter_email.help_text }}
    + +
    +
    {{ form.body }}
    + {% if form.body.errors %} +
    {{ form.body.errors }}
    {% endif %} + +
    +
    {{ form.priority }}
    + {% if form.priority.errors %} +
    {{ form.priority.errors }}
    {% endif %} +
    {{ form.priority.help_text }}
    +
    + +
    + +
    +
    + +
    +{% endblock %} diff --git a/templates/helpdesk/public_view_form.html b/templates/helpdesk/public_view_form.html new file mode 100644 index 00000000..2626102d --- /dev/null +++ b/templates/helpdesk/public_view_form.html @@ -0,0 +1,23 @@ +{% extends "helpdesk/public_base.html" %} +{% block helpdesk_title %}Helpdesk{% endblock %} + +{% block helpdesk_body %} +

    View a Ticket

    + +
    + +{% if error_message %}

    Error: {{ error_message }}

    {% endif %} + +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    +{% endblock %} diff --git a/templates/helpdesk/public_view_ticket.html b/templates/helpdesk/public_view_ticket.html new file mode 100644 index 00000000..2c08cbe9 --- /dev/null +++ b/templates/helpdesk/public_view_ticket.html @@ -0,0 +1,65 @@ +{% extends "helpdesk/public_base.html" %} +{% block helpdesk_title %}Helpdesk{% endblock %} +{% block helpdesk_head %}{% load markup %} + + +{% endblock %} + +{% block helpdesk_body %} + + + + + + + + + + + + + + + + + + + + + + + + + + + +{% if ticket.resolution %} + + + + +{% endif %} + +
    {{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status_display }}]
    Queue: {{ ticket.queue }}
    Submitted On{{ ticket.created|date:"r" }} ({{ ticket.created|timesince }} ago)
    Submitter E-Mail{{ ticket.submitter_email }}
    Priority{{ ticket.get_priority_display }}
    Description
    {{ ticket.description }}
    Resolution{% ifequal ticket.get_status_display "Resolved" %} Accept{% endifequal %}
    {{ ticket.resolution }}
    + +{% if ticket.followup_set.public_followups %} +

    Follow-Ups

    +{% load ticket_to_link %} +{% for followup in ticket.followup_set.public_followups %} +
    +
    {{ followup.title }}
    +{{ followup.comment|num_to_link }} +{% if followup.ticketchange_set.all %}
    +{% for change in followup.ticketchange_set.all %} +Changed {{ change.field }} from {{ change.old_value }} to {{ change.new_value }}.
    +{% endfor %} +
    {% endif %} +
    +{% endfor %} +{% endif %} + +{% endblock %} diff --git a/templates/registration/login.html b/templates/registration/login.html index 80b4a36f..e9bb0bc4 100644 --- a/templates/registration/login.html +++ b/templates/registration/login.html @@ -1,4 +1,4 @@ -{% extends "helpdesk/base.html" %} +{% extends "helpdesk/public_base.html" %} {% block helpdesk_title %}Helpdesk Login{% endblock %} {% block helpdesk_body %} diff --git a/templates/registration/logout.html b/templates/registration/logout.html new file mode 100644 index 00000000..28af317d --- /dev/null +++ b/templates/registration/logout.html @@ -0,0 +1,9 @@ +{% extends "helpdesk/public_base.html" %} +{% block helpdesk_title %}Helpdesk{% endblock %} + +{% block helpdesk_body %} +

    Logout

    + +

    Thanks for being here. Hopefully you've helped resolve a few tickets and make the world a better place.

    + +{% endblock %} diff --git a/urls.py b/urls.py index 32f1c31e..8a97036e 100644 --- a/urls.py +++ b/urls.py @@ -58,6 +58,10 @@ urlpatterns = patterns('helpdesk.views', url(r'^raw/(?P\w+)/$', 'raw_details', name='helpdesk_raw'), + + url(r'^view/$', + 'public_view', + name='helpdesk_public_view'), ) urlpatterns += patterns('', diff --git a/views.py b/views.py index 00378652..f0e5af7c 100644 --- a/views.py +++ b/views.py @@ -39,29 +39,50 @@ from django.shortcuts import render_to_response, get_object_or_404 from django.template import loader, Context, RequestContext # Helpdesk imports -from helpdesk.forms import TicketForm +from helpdesk.forms import TicketForm, PublicTicketForm from helpdesk.lib import send_multipart_mail from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply def dashboard(request): - tickets = Ticket.objects.filter(assigned_to=request.user).exclude(status=Ticket.CLOSED_STATUS) - unassigned_tickets = Ticket.objects.filter(assigned_to__isnull=True).exclude(status=Ticket.CLOSED_STATUS) + """ + This isn't always truly a "dashboard" view. If the user is not logged in, we + instead show the user a "public submission" form and a way to view existing + tickets. + """ + if request.user.is_authenticated(): + tickets = Ticket.objects.filter(assigned_to=request.user).exclude(status=Ticket.CLOSED_STATUS) + unassigned_tickets = Ticket.objects.filter(assigned_to__isnull=True).exclude(status=Ticket.CLOSED_STATUS) - dash_tickets = [] - for q in Queue.objects.all(): - dash_tickets.append({ - 'queue': q, - 'open': q.ticket_set.filter(Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)).count(), - 'resolved': q.ticket_set.filter(status=Ticket.RESOLVED_STATUS).count(), - }) + dash_tickets = [] + for q in Queue.objects.all(): + dash_tickets.append({ + 'queue': q, + 'open': q.ticket_set.filter(Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)).count(), + 'resolved': q.ticket_set.filter(status=Ticket.RESOLVED_STATUS).count(), + }) - return render_to_response('helpdesk/dashboard.html', - RequestContext(request, { - 'user_tickets': tickets, - 'unassigned_tickets': unassigned_tickets, - 'dash_tickets': dash_tickets, - })) -dashboard = login_required(dashboard) + return render_to_response('helpdesk/dashboard.html', + RequestContext(request, { + 'user_tickets': tickets, + 'unassigned_tickets': unassigned_tickets, + 'dash_tickets': dash_tickets, + })) + else: + # Not a logged in user + if request.method == 'POST': + form = PublicTicketForm(request.POST) + form.fields['queue'].choices = [('', '--------')] + [[q.id, q.title] for q in Queue.objects.all()] + if form.is_valid(): + ticket = form.save() + return HttpResponseRedirect('%s?ticket=%s&email=%s'% (reverse('helpdesk_public_view'), ticket.ticket_for_url, ticket.submitter_email)) + else: + form = PublicTicketForm() + form.fields['queue'].choices = [('', '--------')] + [[q.id, q.title] for q in Queue.objects.all()] + + return render_to_response('helpdesk/public_homepage.html', + RequestContext(request, { + 'form': form, + })) def delete_ticket(request, ticket_id): ticket = get_object_or_404(Ticket, id=ticket_id) @@ -260,3 +281,25 @@ def raw_details(request, type): raise Http404 raw_details = login_required(raw_details) + +def public_view(request): + ticket = request.GET.get('ticket', '') + email = request.GET.get('email', '') + error_message = '' + + if ticket and email: + queue, ticket_id = ticket.split('-') + try: + t = Ticket.objects.get(id=ticket_id, queue__slug__iexact=queue, submitter_email__iexact=email) + return render_to_response('helpdesk/public_view_ticket.html', + RequestContext(request, {'ticket': t,})) + except: + t = False; + error_message = 'Invalid ticket ID or e-mail address. Please try again.' + + return render_to_response('helpdesk/public_view_form.html', + RequestContext(request, { + 'ticket': ticket, + 'email': email, + 'error_message': error_message, + }))