Initial shot at custom fields on Ticket model. Manage these fields via Django admin.

This commit is contained in:
Ross Poulton 2011-02-02 11:22:46 +00:00
parent 723b4f1881
commit ebe0382725
5 changed files with 253 additions and 20 deletions

View File

@ -2,6 +2,7 @@ from django.contrib import admin
from helpdesk.models import Queue, Ticket, FollowUp, PreSetReply, KBCategory
from helpdesk.models import EscalationExclusion, EmailTemplate, KBItem
from helpdesk.models import TicketChange, Attachment, IgnoreEmail
from helpdesk.models import CustomField
class QueueAdmin(admin.ModelAdmin):
list_display = ('title', 'slug', 'email_address')
@ -23,6 +24,9 @@ class FollowUpAdmin(admin.ModelAdmin):
class KBItemAdmin(admin.ModelAdmin):
list_display = ('category', 'title', 'last_updated',)
list_display_links = ('title',)
class CustomFieldAdmin(admin.ModelAdmin):
list_display = ('name', 'label', 'data_type')
admin.site.register(Ticket, TicketAdmin)
admin.site.register(Queue, QueueAdmin)
@ -33,3 +37,4 @@ admin.site.register(EmailTemplate)
admin.site.register(KBCategory)
admin.site.register(KBItem, KBItemAdmin)
admin.site.register(IgnoreEmail)
admin.site.register(CustomField, CustomFieldAdmin)

View File

@ -15,7 +15,7 @@ from django.contrib.auth.models import User
from django.utils.translation import ugettext as _
from helpdesk.lib import send_templated_mail
from helpdesk.models import Ticket, Queue, FollowUp, Attachment, IgnoreEmail, TicketCC
from helpdesk.models import Ticket, Queue, FollowUp, Attachment, IgnoreEmail, TicketCC, CustomField, TicketCustomFieldValue
from helpdesk.settings import HAS_TAG_SUPPORT
class EditTicketForm(forms.ModelForm):
@ -93,6 +93,56 @@ class TicketForm(forms.Form):
'ticket'),
)
def __init__(self, *args, **kwargs):
"""
Add any custom fields that are defined to the form
"""
super(TicketForm, self).__init__(*args, **kwargs)
for field in CustomField.objects.all():
instanceargs = {
'label': field.label,
'help_text': field.help_text,
'required': field.required,
}
if field.data_type == 'varchar':
fieldclass = forms.CharField
instanceargs['max_length'] = field.max_length
elif field.data_type == 'text':
fieldclass = forms.CharField
instanceargs['widget'] = forms.Textarea
instanceargs['max_length'] = field.max_length
elif field.data_type == 'integer':
fieldclass = forms.IntegerField
elif field.data_type == 'decimal':
fieldclass = forms.DecimalField
instanceargs['decimal_places'] = field.decimal_places
instanceargs['max_digits'] = field.max_length
elif field.data_type == 'list':
fieldclass = forms.ChoiceField
choices = []
for line in field.list_values.split("\n"):
choices.append((line, line))
instanceargs['choices'] = choices
elif field.data_type == 'boolean':
fieldclass = forms.BooleanField
elif field.data_type == 'date':
fieldclass = forms.DateField
elif field.data_type == 'time':
fieldclass = forms.TimeField
elif field.data_type == 'datetime':
fieldclass = forms.DateTimeField
elif field.data_type == 'email':
fieldclass = forms.EmailField
elif field.data_type == 'url':
fieldclass = forms.URLField
elif field.data_type == 'ipaddress':
fieldclass = forms.IPAddressField
elif field.data_type == 'slug':
fieldclass = forms.SlugField
self.fields['custom_%s' % field.name] = fieldclass(**instanceargs)
def save(self, user):
"""
Writes and returns a Ticket() object
@ -119,6 +169,15 @@ class TicketForm(forms.Form):
except User.DoesNotExist:
t.assigned_to = None
t.save()
for field, value in self.cleaned_data.items():
if field.startswith('custom_'):
field_name = field.replace('custom_', '')
customfield = CustomField.objects.get(name=field_name)
cfv = TicketCustomFieldValue(ticket=t,
field=customfield,
value=value)
cfv.save()
f = FollowUp( ticket = t,
title = _('Ticket Opened'),
@ -249,6 +308,54 @@ class PublicTicketForm(forms.Form):
help_text=_('You can attach a file such as a document or screenshot to this ticket.'),
)
def __init__(self, *args, **kwargs):
"""
Add any custom fields that are defined to the form
"""
super(PublicTicketForm, self).__init__(*args, **kwargs)
for field in CustomField.objects.filter(staff_only=False):
instanceargs = {
'label': field.label,
'help_text': field.help_text,
'required': field.required,
}
if field.data_type == 'varchar':
fieldclass = forms.CharField
instanceargs['max_length'] = field.max_length
elif field.data_type == 'text':
fieldclass = forms.TextField
instanceargs['max_length'] = field.max_length
elif field.data_type == 'integer':
fieldclass = forms.IntegerField
elif field.data_type == 'decimal':
fieldclass = forms.DecimalField
instanceargs['decimal_places'] = field.decimal_places
instanceargs['max_digits'] = field.max_length
elif field.data_type == 'list':
fieldclass = forms.ChoiceField
choices = ()
for line in field.choices:
choices.append((line, line))
instanceargs['choices'] = choices
elif field.data_type == 'boolean':
fieldclass = forms.BooleanField
elif field.data_type == 'date':
fieldclass = forms.DateField
elif field.data_type == 'time':
fieldclass = forms.TimeField
elif field.data_type == 'datetime':
fieldclass = forms.DateTimeField
elif field.data_type == 'email':
fieldclass = forms.EmailField
elif field.data_type == 'url':
fieldclass = forms.URLField
elif field.data_type == 'ipaddress':
fieldclass = forms.IPAddressField
elif field.data_type == 'slug':
fieldclass = forms.SlugField
self.fields['custom_%s' % field.name] = fieldclass(**instanceargs)
def save(self):
"""
Writes and returns a Ticket() object
@ -268,6 +375,15 @@ class PublicTicketForm(forms.Form):
t.save()
for field, value in self.cleaned_data.items():
if field.startswith('custom_'):
field_name = field.replace('custom_', '')
customfield = CustomField.objects.get(name=field_name)
cfv = TicketCustomFieldValue(ticket=t,
field=customfield,
value=value)
cfv.save()
f = FollowUp(
ticket = t,
title = _('Ticket Opened Via Web'),

View File

@ -1085,3 +1085,102 @@ class TicketCC(models.Model):
def __unicode__(self):
return u'%s for %s' % (self.display, self.ticket.title)
class CustomField(models.Model):
"""
Definitions for custom fields that are glued onto each ticket.
"""
name = models.SlugField(
_('Field Name'),
help_text=_('As used in the database and behind the scenes. Must be unique and consist of only lowercase letters with no punctuation.'),
unique=True,
)
label = models.CharField(
_('Label'),
max_length='30',
help_text=_('The display label for this field'),
)
help_text = models.TextField(
_('Help Text'),
help_text=_('Shown to the user when editing the ticket'),
)
DATA_TYPE_CHOICES = (
('varchar', _('Character (single line)')),
('text', _('Text (multi-line)')),
('integer', _('Integer')),
('decimal', _('Decimal')),
('list', _('List')),
('boolean', _('Boolean (checkbox yes/no)')),
('date', _('Date')),
('time', _('Time')),
('datetime', _('Date & Time')),
('email', _('E-Mail Address')),
('url', _('URL')),
('ipaddress', _('IP Address')),
('slug', _('Slug')),
)
data_type = models.CharField(
_('Data Type'),
max_length=100,
help_text=_('Allows you to restrict the data entered into this field'),
choices=DATA_TYPE_CHOICES,
)
max_length = models.IntegerField(
_('Maximum Length (characters)'),
blank=True,
null=True,
)
decimal_places = models.IntegerField(
_('Decimal Places'),
help_text=_('Only used for decimal fields'),
blank=True,
null=True,
)
list_values = models.TextField(
_('List Values'),
help_text=_('For list fields only. Enter one option per line.'),
blank=True,
null=True,
)
required = models.BooleanField(
_('Required?'),
help_text=_('Does the user have to enter a value for this field?'),
)
staff_only = models.BooleanField(
_('Staff Only?'),
help_text=_('If this is ticked, then the public submission form will NOT show this field'),
)
def __unicode__(self):
return '%s' % (self.name)
class TicketCustomFieldValue(models.Model):
ticket = models.ForeignKey(
Ticket,
verbose_name=_('Ticket'),
)
field = models.ForeignKey(
CustomField,
verbose_name=_('Field'),
)
value = models.TextField(blank=True, null=True)
def __unicode__(self):
return '%s / %s' % (self.ticket, self.field)
class Meta:
unique_together = ('ticket', 'field'),

View File

@ -1,4 +1,5 @@
{% extends "helpdesk/public_base.html" %}{% load i18n %}
{% cycle 'row_odd' 'row_even' as rowcolors %}
{% block helpdesk_title %}{% trans "View a Ticket" %}{% endblock %}
{% block helpdesk_body %}
@ -7,39 +8,45 @@
<tr class='row_tablehead'><td colspan='2'>{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}]</td></tr>
<tr class='row_columnheads'><th colspan='2'>{% blocktrans with ticket.queue as queue_name %}Queue: {{ queue_name }}{% endblocktrans %}</th></tr>
<tr class='row_odd'>
<tr class='{% cycle rowcolors %}'>
<th>{% trans "Submitted On" %}</th>
<td>{{ ticket.created|date:"r" }} ({{ ticket.created|timesince }} ago)</td>
</tr>
<tr class='row_even'>
<tr class='{% cycle rowcolors %}'>
<th>{% trans "Submitter E-Mail" %}</th>
<td>{{ ticket.submitter_email }}</td>
</tr>
<tr class='row_odd'>
<tr class='{% cycle rowcolors %}'>
<th>{% trans "Priority" %}</th>
<td>{{ ticket.get_priority_display }}</td>
</tr>
{% for customfield in ticket.ticketcustomfieldvalue_set.all %}
<tr class='{% cycle rowcolors %}'>
<th>{{ customfield.field.label }}</th>
<td>{{ customfield.value }}</td>
</tr>{% endfor %}
{% if tags_enabled %}
<tr class='row_even'>
<tr class='{% cycle rowcolors %}'>
<th>{% trans "Tags" %}</th>
<td>{{ ticket.tags }}</td>
</tr>
{% endif %}
<tr class='row_{% if tags_enabled %}odd{% else %}even{% endif %}'>
<tr class='{% cycle rowcolors %}'>
<th colspan='2'>{% trans "Description" %}</th>
</tr>
<tr class='row_{% if tags_enabled %}odd{% else %}even{% endif %}'>
<tr class='{% cycle rowcolors %}'>
<td colspan='2'>{{ ticket.description|force_escape|urlizetrunc:50|linebreaksbr }}</td>
</tr>
{% if ticket.resolution %}<tr class='row_{% if tags_enabled %}even{% else %}odd{% endif %}'>
{% if ticket.resolution %}<tr class='{% cycle rowcolors %}'>
<th colspan='2'>{% trans "Resolution" %}{% ifequal ticket.get_status_display "Resolved" %} <a href='{{ request.get_full_path }}&close'><img src='{{ MEDIA_URL }}/helpdesk/buttons/accept.png' alt='{% trans "Accept" %}' title='{% trans "Accept and Close" %}' width='60' height='15' /></a>{% endifequal %}</th>
</tr>
<tr class='row_{% if tags_enabled %}even{% else %}odd{% endif %}'>
<tr class='{% cycle rowcolors %}'>
<td colspan='2'>{{ ticket.resolution|urlizetrunc:50|linebreaksbr }}</td>
</tr>{% endif %}

View File

@ -1,52 +1,58 @@
{% load i18n %}
{% cycle 'row_odd' 'row_even' as rowcolors %}
<table width='100%'>
<tr class='row_tablehead'><td>{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}]</td><td align='right'><a href='{% url helpdesk_edit ticket.id %}'><img src='{{ MEDIA_URL }}helpdesk/buttons/edit.png' alt='Edit' title='Edit' width='60' height='15' /></a><a href='{% url helpdesk_delete ticket.id %}'><img src='{{ MEDIA_URL }}helpdesk/buttons/delete.png' alt='Delete' title='Delete' width='60' height='15' /></a>{% if ticket.on_hold %}<a href='unhold/'>{% trans "Unhold" %}</a>{% else %}<a href='hold/'>{% trans "Hold" %}</a>{% endif %}</td></tr>
<tr class='row_columnheads'><th colspan='2'>{% blocktrans with ticket.queue as queue %}Queue: {{ queue }}{% endblocktrans %}</th></tr>
<tr class='row_odd'>
<tr class='{% cycle rowcolors %}'>
<th>{% trans "Submitted On" %}</th>
<td>{{ ticket.created|date:"r" }} ({{ ticket.created|timesince }} ago)</td>
</tr>
<tr class='row_even'>
<tr class='{% cycle rowcolors %}'>
<th>{% trans "Assigned To" %}</th>
<td>{{ ticket.get_assigned_to }}{% ifequal ticket.get_assigned_to _('Unassigned') %} <strong><a href='?take'><span class='button button_take'>{% trans "Take" %}</span></a></strong>{% endifequal %}</td>
</tr>
<tr class='row_odd'>
<tr class='{% cycle rowcolors %}'>
<th>{% trans "Submitter E-Mail" %}</th>
<td>{{ ticket.submitter_email }}{% if user.is_superuser %} <strong><a href='{% url helpdesk_email_ignore_add %}?email={{ ticket.submitter_email }}'>{% trans "Ignore" %}</a></strong>{% endif %}</td>
</tr>
<tr class='row_even'>
<tr class='{% cycle rowcolors %}'>
<th>{% trans "Priority" %}</th>
<td>{{ ticket.get_priority_display }}</td>
</tr>
<tr class='row_odd'>
<tr class='{% cycle rowcolors %}'>
<th>{% trans "Copies To" %}</th>
<td>{% for ticketcc in ticket.ticketcc_set.all %}{{ ticketcc.display }}{% if not forloop.last %}, {% endif %}{% endfor %} <strong><a href='{% url helpdesk_ticket_cc ticket.id %}'>{% trans "Manage" %}</a></strong></td>
</tr>
{% if tags_enabled %}
<tr class='row_even'>
<tr class='{% cycle rowcolors %}'>
<th>{% trans "Tags" %}</th>
<td>{{ ticket.tags }}</td>
</tr>
{% endif %}
<tr class='row_even'>
<tr class='row_{% if tags_enabled %}odd{% else %}even{% endif %}'>
{% for customfield in ticket.ticketcustomfieldvalue_set.all %}
<tr class='{% cycle rowcolors %}'>
<th>{{ customfield.field.label }}</th>
<td>{{ customfield.value }}</td>
</tr>{% endfor %}
<tr class='{% cycle rowcolors %}'>
<th colspan='2'>{% trans "Description" %}</th>
</tr>
<tr class='row_{% if tags_enabled %}even{% else %}odd{% endif %}'>
<tr class='{% cycle rowcolors %}'>
<td colspan='2'>{{ ticket.description|force_escape|urlizetrunc:50|linebreaksbr }}</td>
</tr>
{% if ticket.resolution %}<tr class='row_{% if tags_enabled %}odd{% else %}even{% endif %}'>
{% if ticket.resolution %}<tr class='{% cycle rowcolors %}'>
<th colspan='2'>{% trans "Resolution" %}{% ifequal ticket.get_status_display "Resolved" %} <a href='?close'><img src='{{ MEDIA_URL }}helpdesk/buttons/accept.png' alt='{% trans "Accept" %}' title='{% trans "Accept and Close" %}' width='60' height='15' /></a>{% endifequal %}</th>
</tr>
<tr class='row_{% if tags_enabled %}even{% else %}odd{% endif %}'>
<tr class='{% cycle rowcolors %}'>
<td colspan='2'>{{ ticket.resolution|force_escape|urlizetrunc:50|linebreaksbr }}</td>
</tr>{% endif %}