forked from extern/django-helpdesk
Add ticket dependency so you can't resolve a ticket until it's dependents are resolved. Addresses GH-43
This commit is contained in:
parent
50e1e1ea23
commit
8b6fbd8965
@ -16,7 +16,7 @@ from django.contrib.auth.models import User
|
|||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from helpdesk.lib import send_templated_mail
|
from helpdesk.lib import send_templated_mail
|
||||||
from helpdesk.models import Ticket, Queue, FollowUp, Attachment, IgnoreEmail, TicketCC, CustomField, TicketCustomFieldValue
|
from helpdesk.models import Ticket, Queue, FollowUp, Attachment, IgnoreEmail, TicketCC, CustomField, TicketCustomFieldValue, TicketDependency
|
||||||
from helpdesk.settings import HAS_TAG_SUPPORT
|
from helpdesk.settings import HAS_TAG_SUPPORT
|
||||||
|
|
||||||
class EditTicketForm(forms.ModelForm):
|
class EditTicketForm(forms.ModelForm):
|
||||||
@ -574,3 +574,7 @@ class TicketCCForm(forms.ModelForm):
|
|||||||
model = TicketCC
|
model = TicketCC
|
||||||
exclude = ('ticket',)
|
exclude = ('ticket',)
|
||||||
|
|
||||||
|
class TicketDependencyForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = TicketDependency
|
||||||
|
exclude = ('ticket',)
|
||||||
|
@ -0,0 +1,237 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
|
||||||
|
# Adding model 'TicketDependency'
|
||||||
|
db.create_table('helpdesk_ticketdependency', (
|
||||||
|
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||||
|
('ticket', self.gf('django.db.models.fields.related.ForeignKey')(related_name='ticket', to=orm['helpdesk.Ticket'])),
|
||||||
|
('depends_on', self.gf('django.db.models.fields.related.ForeignKey')(related_name='depends_on', to=orm['helpdesk.Ticket'])),
|
||||||
|
))
|
||||||
|
db.send_create_signal('helpdesk', ['TicketDependency'])
|
||||||
|
|
||||||
|
# Adding unique constraint on 'TicketDependency', fields ['ticket', 'depends_on']
|
||||||
|
db.create_unique('helpdesk_ticketdependency', ['ticket_id', 'depends_on_id'])
|
||||||
|
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
|
||||||
|
# Removing unique constraint on 'TicketDependency', fields ['ticket', 'depends_on']
|
||||||
|
db.delete_unique('helpdesk_ticketdependency', ['ticket_id', 'depends_on_id'])
|
||||||
|
|
||||||
|
# Deleting model 'TicketDependency'
|
||||||
|
db.delete_table('helpdesk_ticketdependency')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'auth.group': {
|
||||||
|
'Meta': {'object_name': 'Group'},
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'unique': 'True'}),
|
||||||
|
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'auth.permission': {
|
||||||
|
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||||
|
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||||
|
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||||
|
},
|
||||||
|
'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', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'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', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'username': ('django.db.models.fields.CharField', [], {'max_length': '30', 'unique': 'True'})
|
||||||
|
},
|
||||||
|
'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'}),
|
||||||
|
'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'})
|
||||||
|
},
|
||||||
|
'helpdesk.attachment': {
|
||||||
|
'Meta': {'ordering': "['filename']", 'object_name': 'Attachment'},
|
||||||
|
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
|
||||||
|
'filename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||||
|
'followup': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helpdesk.FollowUp']"}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
|
||||||
|
'size': ('django.db.models.fields.IntegerField', [], {})
|
||||||
|
},
|
||||||
|
'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'}),
|
||||||
|
'help_text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||||
|
'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', [], {'max_length': '50', 'unique': 'True', 'db_index': 'True'}),
|
||||||
|
'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'staff_only': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
|
||||||
|
},
|
||||||
|
'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', [], {}),
|
||||||
|
'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'})
|
||||||
|
},
|
||||||
|
'helpdesk.escalationexclusion': {
|
||||||
|
'Meta': {'object_name': 'EscalationExclusion'},
|
||||||
|
'date': ('django.db.models.fields.DateField', [], {}),
|
||||||
|
'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', [], {'to': "orm['helpdesk.Queue']", 'symmetrical': 'False', 'null': 'True', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'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(2011, 5, 10, 12, 20, 8, 45416)'}),
|
||||||
|
'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': "orm['helpdesk.Ticket']"}),
|
||||||
|
'title': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
|
||||||
|
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'helpdesk.ignoreemail': {
|
||||||
|
'Meta': {'object_name': 'IgnoreEmail'},
|
||||||
|
'date': ('django.db.models.fields.DateField', [], {'blank': 'True'}),
|
||||||
|
'email_address': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
|
||||||
|
'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', [], {'to': "orm['helpdesk.Queue']", 'symmetrical': 'False', 'null': 'True', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'helpdesk.kbcategory': {
|
||||||
|
'Meta': {'ordering': "['title']", 'object_name': 'KBCategory'},
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
|
||||||
|
'title': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||||
|
},
|
||||||
|
'helpdesk.kbitem': {
|
||||||
|
'Meta': {'ordering': "['title']", 'object_name': 'KBItem'},
|
||||||
|
'answer': ('django.db.models.fields.TextField', [], {}),
|
||||||
|
'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helpdesk.KBCategory']"}),
|
||||||
|
'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'})
|
||||||
|
},
|
||||||
|
'helpdesk.presetreply': {
|
||||||
|
'Meta': {'ordering': "['name']", 'object_name': 'PreSetReply'},
|
||||||
|
'body': ('django.db.models.fields.TextField', [], {}),
|
||||||
|
'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', [], {'to': "orm['helpdesk.Queue']", 'symmetrical': 'False', 'null': 'True', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'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'}),
|
||||||
|
'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', 'db_index': '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'})
|
||||||
|
},
|
||||||
|
'helpdesk.savedsearch': {
|
||||||
|
'Meta': {'object_name': 'SavedSearch'},
|
||||||
|
'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': "orm['auth.User']"})
|
||||||
|
},
|
||||||
|
'helpdesk.ticket': {
|
||||||
|
'Meta': {'object_name': 'Ticket'},
|
||||||
|
'assigned_to': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'assigned_to'", 'blank': 'True', 'null': 'True', 'to': "orm['auth.User']"}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'blank': 'True'}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||||
|
'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': "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'})
|
||||||
|
},
|
||||||
|
'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'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helpdesk.Ticket']"}),
|
||||||
|
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'helpdesk.ticketchange': {
|
||||||
|
'Meta': {'object_name': 'TicketChange'},
|
||||||
|
'field': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||||
|
'followup': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helpdesk.FollowUp']"}),
|
||||||
|
'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'})
|
||||||
|
},
|
||||||
|
'helpdesk.ticketcustomfieldvalue': {
|
||||||
|
'Meta': {'unique_together': "(('ticket', 'field'),)", 'object_name': 'TicketCustomFieldValue'},
|
||||||
|
'field': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helpdesk.CustomField']"}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helpdesk.Ticket']"}),
|
||||||
|
'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'helpdesk.ticketdependency': {
|
||||||
|
'Meta': {'unique_together': "(('ticket', 'depends_on'),)", 'object_name': 'TicketDependency'},
|
||||||
|
'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'depends_on'", 'to': "orm['helpdesk.Ticket']"}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'ticket': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ticket'", 'to': "orm['helpdesk.Ticket']"})
|
||||||
|
},
|
||||||
|
'helpdesk.usersettings': {
|
||||||
|
'Meta': {'object_name': 'UserSettings'},
|
||||||
|
'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': "orm['auth.User']", 'unique': 'True'})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_apps = ['helpdesk']
|
@ -413,6 +413,16 @@ class Ticket(models.Model):
|
|||||||
)
|
)
|
||||||
staff_url = property(_get_staff_url)
|
staff_url = property(_get_staff_url)
|
||||||
|
|
||||||
|
def _can_be_resolved(self):
|
||||||
|
"""
|
||||||
|
Returns a boolean.
|
||||||
|
True = any dependencies are resolved
|
||||||
|
False = There are non-resolved dependencies
|
||||||
|
"""
|
||||||
|
OPEN_STATUSES = (Ticket.OPEN_STATUS, Ticket.REOPENED_STATUS)
|
||||||
|
return TicketDependency.objects.filter(ticket=self).filter(depends_on__status__in=OPEN_STATUSES).count() == 0
|
||||||
|
can_be_resolved = property(_can_be_resolved)
|
||||||
|
|
||||||
if HAS_TAG_SUPPORT:
|
if HAS_TAG_SUPPORT:
|
||||||
tags = TagField(blank=True)
|
tags = TagField(blank=True)
|
||||||
|
|
||||||
@ -1209,3 +1219,28 @@ class TicketCustomFieldValue(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('ticket', 'field'),
|
unique_together = ('ticket', 'field'),
|
||||||
|
|
||||||
|
|
||||||
|
class TicketDependency(models.Model):
|
||||||
|
"""
|
||||||
|
The ticket identified by `ticket` cannot be resolved until the ticket in `depends_on` has been resolved.
|
||||||
|
To help enforce this, a helper function `can_be_resolved` on each Ticket instance checks that
|
||||||
|
these have all been resolved.
|
||||||
|
"""
|
||||||
|
ticket = models.ForeignKey(
|
||||||
|
Ticket,
|
||||||
|
verbose_name=_('Ticket'),
|
||||||
|
related_name='ticketdependency',
|
||||||
|
)
|
||||||
|
|
||||||
|
depends_on = models.ForeignKey(
|
||||||
|
Ticket,
|
||||||
|
verbose_name=_('Depends On Ticket'),
|
||||||
|
related_name='depends_on',
|
||||||
|
)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return '%s / %s' % (self.ticket, self.depends_on)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('ticket', 'depends_on')
|
||||||
|
@ -82,16 +82,17 @@
|
|||||||
<dd class='form_help_text'>{% trans "You can insert ticket and queue details in your message. For more information, see the <a href='../../help/context/'>context help page</a>." %}</dd>
|
<dd class='form_help_text'>{% trans "You can insert ticket and queue details in your message. For more information, see the <a href='../../help/context/'>context help page</a>." %}</dd>
|
||||||
|
|
||||||
<dt><label>{% trans "New Status" %}</label></dt>
|
<dt><label>{% trans "New Status" %}</label></dt>
|
||||||
|
{% if not ticket.can_be_resolved %}<dd>{% trans "This ticket cannot be resolved or closed until the tickets it depends on are resolved." %}</dd>{% endif %}
|
||||||
{% ifequal ticket.status 1 %}
|
{% ifequal ticket.status 1 %}
|
||||||
<dd><input type='radio' name='new_status' value='1' id='st_open' checked='checked'><label for='st_open' class='active'>{% trans "Open" %}</label> »
|
<dd><input type='radio' name='new_status' value='1' id='st_open' checked='checked'><label for='st_open' class='active'>{% trans "Open" %}</label> »
|
||||||
<input type='radio' name='new_status' value='3' id='st_resolved'><label for='st_resolved'>{% trans "Resolved" %}</label> »
|
<input type='radio' name='new_status' value='3' id='st_resolved'{% if not ticket.can_be_resolved %} disabled='disabled'{% endif %}><label for='st_resolved'>{% trans "Resolved" %}</label> »
|
||||||
<input type='radio' name='new_status' value='4' id='st_closed'><label for='st_closed'>{% trans "Closed" %}</label> »
|
<input type='radio' name='new_status' value='4' id='st_closed'{% if not ticket.can_be_resolved %} disabled='disabled'{% endif %}><label for='st_closed'>{% trans "Closed" %}</label> »
|
||||||
<input type='radio' name='new_status' value='5' id='st_duplicate'><label for='st_duplicate'>{% trans "Duplicate" %}</label></dd>
|
<input type='radio' name='new_status' value='5' id='st_duplicate'><label for='st_duplicate'>{% trans "Duplicate" %}</label></dd>
|
||||||
{% endifequal %}
|
{% endifequal %}
|
||||||
{% ifequal ticket.status 2 %}
|
{% ifequal ticket.status 2 %}
|
||||||
<dd><input type='radio' name='new_status' value='2' id='st_reopened' checked='checked'><label for='st_reopened' class='active'>{% trans "Reopened" %}</label> »
|
<dd><input type='radio' name='new_status' value='2' id='st_reopened' checked='checked'><label for='st_reopened' class='active'>{% trans "Reopened" %}</label> »
|
||||||
<input type='radio' name='new_status' value='3' id='st_resolved'><label for='st_resolved'>{% trans "Resolved" %}</label> »
|
<input type='radio' name='new_status' value='3' id='st_resolved'{% if not ticket.can_be_resolved %} disabled='disabled'{% endif %}><label for='st_resolved'>{% trans "Resolved" %}</label> »
|
||||||
<input type='radio' name='new_status' value='4' id='st_closed'><label for='st_closed'>{% trans "Closed" %}</label> »
|
<input type='radio' name='new_status' value='4' id='st_closed'{% if not ticket.can_be_resolved %} disabled='disabled'{% endif %}><label for='st_closed'>{% trans "Closed" %}</label> »
|
||||||
<input type='radio' name='new_status' value='5' id='st_duplicate'><label for='st_duplicate'>{% trans "Duplicate" %}</label></dd>
|
<input type='radio' name='new_status' value='5' id='st_duplicate'><label for='st_duplicate'>{% trans "Duplicate" %}</label></dd>
|
||||||
{% endifequal %}
|
{% endifequal %}
|
||||||
{% ifequal ticket.status 3 %}
|
{% ifequal ticket.status 3 %}
|
||||||
|
25
helpdesk/templates/helpdesk/ticket_dependency_add.html
Normal file
25
helpdesk/templates/helpdesk/ticket_dependency_add.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{% extends "helpdesk/base.html" %}{% load i18n %}
|
||||||
|
|
||||||
|
{% block helpdesk_title %}{% trans "Add Ticket Dependency" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block helpdesk_body %}{% blocktrans %}
|
||||||
|
<h2>Add Ticket Dependency</h2>
|
||||||
|
|
||||||
|
<p>Adding a dependency will stop you resolving this ticket until the dependent ticket has been resolved or closed.</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 Ticket Dependency" %}' />
|
||||||
|
|
||||||
|
{% csrf_token %}</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
14
helpdesk/templates/helpdesk/ticket_dependency_del.html
Normal file
14
helpdesk/templates/helpdesk/ticket_dependency_del.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{% extends "helpdesk/base.html" %}{% load i18n %}
|
||||||
|
|
||||||
|
{% block helpdesk_title %}{% trans "Delete Ticket Dependency" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block helpdesk_body %}{% blocktrans %}
|
||||||
|
<h2>Delete Ticket Dependency</h2>
|
||||||
|
|
||||||
|
<p>Are you sure you wish to remove the dependency on this ticket?</p>
|
||||||
|
{% endblocktrans %}
|
||||||
|
|
||||||
|
<p><a href='../../'>{% trans "Don't Delete" %}</a></p>
|
||||||
|
|
||||||
|
<form method='post' action='./'><input type='submit' value='{% trans "Yes, Delete" %}' />{% csrf_token %}</form>
|
||||||
|
{% endblock %}
|
@ -35,6 +35,19 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<tr class='{% cycle rowcolors %}'>
|
||||||
|
<th>{% trans "Dependencies" %}</th>
|
||||||
|
<td>{% for dep in ticket.ticketdependency.all %}
|
||||||
|
{% if forloop.first %}<p>{% trans "This ticket cannot be resolved until the following ticket(s) are resolved" %}</p><ul>{% endif %}
|
||||||
|
<li><a href='{{ dep.depends_on.get_absolute_url }}'>{{ dep.depends_on.ticket }} {{ dep.depends_on.title }}</a> ({{ dep.depends_on.get_status_display }}) <a href='{% url helpdesk_ticket_dependency_del ticket.id dep.id %}'>{% trans "Remove Dependency" %}</a></li>
|
||||||
|
{% if forloop.last %}</ul>{% endif %}
|
||||||
|
{% empty %}
|
||||||
|
<p>{% trans "This ticket has no dependencies." %}</p>
|
||||||
|
{% endfor %}
|
||||||
|
<p><a href='{% url helpdesk_ticket_dependency_add ticket.id %}'>{% trans "Add Dependency" %}</a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
{% for customfield in ticket.ticketcustomfieldvalue_set.all %}
|
{% for customfield in ticket.ticketcustomfieldvalue_set.all %}
|
||||||
<tr class='{% cycle rowcolors %}'>
|
<tr class='{% cycle rowcolors %}'>
|
||||||
<th>{{ customfield.field.label }}</th>
|
<th>{{ customfield.field.label }}</th>
|
||||||
|
@ -70,7 +70,15 @@ urlpatterns = patterns('helpdesk.views.staff',
|
|||||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/cc/delete/(?P<cc_id>[0-9]+)/$',
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/cc/delete/(?P<cc_id>[0-9]+)/$',
|
||||||
'ticket_cc_del',
|
'ticket_cc_del',
|
||||||
name='helpdesk_ticket_cc_del'),
|
name='helpdesk_ticket_cc_del'),
|
||||||
|
|
||||||
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/dependency/add/$',
|
||||||
|
'ticket_dependency_add',
|
||||||
|
name='helpdesk_ticket_dependency_add'),
|
||||||
|
|
||||||
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/dependency/delete/(?P<dependency_id>[0-9]+)/$',
|
||||||
|
'ticket_dependency_del',
|
||||||
|
name='helpdesk_ticket_dependency_del'),
|
||||||
|
|
||||||
url(r'^raw/(?P<type>\w+)/$',
|
url(r'^raw/(?P<type>\w+)/$',
|
||||||
'raw_details',
|
'raw_details',
|
||||||
name='helpdesk_raw'),
|
name='helpdesk_raw'),
|
||||||
|
@ -23,9 +23,9 @@ from django.template import loader, Context, RequestContext
|
|||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
|
|
||||||
from helpdesk.forms import TicketForm, UserSettingsForm, EmailIgnoreForm, EditTicketForm, TicketCCForm, EditFollowUpForm
|
from helpdesk.forms import TicketForm, UserSettingsForm, EmailIgnoreForm, EditTicketForm, TicketCCForm, EditFollowUpForm, TicketDependencyForm
|
||||||
from helpdesk.lib import send_templated_mail, line_chart, bar_chart, query_to_dict, apply_query, safe_template_context
|
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, IgnoreEmail, TicketCC
|
from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment, SavedSearch, IgnoreEmail, TicketCC, TicketDependency
|
||||||
from helpdesk.settings import HAS_TAG_SUPPORT
|
from helpdesk.settings import HAS_TAG_SUPPORT
|
||||||
|
|
||||||
if HAS_TAG_SUPPORT:
|
if HAS_TAG_SUPPORT:
|
||||||
@ -1037,3 +1037,34 @@ def ticket_cc_del(request, ticket_id, cc_id):
|
|||||||
'cc': cc,
|
'cc': cc,
|
||||||
}))
|
}))
|
||||||
ticket_cc_del = staff_member_required(ticket_cc_del)
|
ticket_cc_del = staff_member_required(ticket_cc_del)
|
||||||
|
|
||||||
|
def ticket_dependency_add(request, ticket_id):
|
||||||
|
ticket = get_object_or_404(Ticket, id=ticket_id)
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = TicketDependencyForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
ticketdependency = form.save(commit=False)
|
||||||
|
ticketdependency.ticket = ticket
|
||||||
|
if ticketdependency.ticket <> ticketdependency.depends_on:
|
||||||
|
ticketdependency.save()
|
||||||
|
return HttpResponseRedirect(reverse('helpdesk_view', args=[ticket.id]))
|
||||||
|
else:
|
||||||
|
form = TicketDependencyForm()
|
||||||
|
return render_to_response('helpdesk/ticket_dependency_add.html',
|
||||||
|
RequestContext(request, {
|
||||||
|
'ticket': ticket,
|
||||||
|
'form': form,
|
||||||
|
}))
|
||||||
|
ticket_dependency_add = staff_member_required(ticket_dependency_add)
|
||||||
|
|
||||||
|
def ticket_dependency_del(request, ticket_id, dependency_id):
|
||||||
|
dependency = get_object_or_404(TicketDependency, ticket__id=ticket_id, id=dependency_id)
|
||||||
|
if request.method == 'POST':
|
||||||
|
dependency.delete()
|
||||||
|
return HttpResponseRedirect(reverse('helpdesk_view', args=[ticket_id]))
|
||||||
|
return render_to_response('helpdesk/ticket_dependency_del.html',
|
||||||
|
RequestContext(request, {
|
||||||
|
'dependency': dependency,
|
||||||
|
}))
|
||||||
|
ticket_dependency_del = staff_member_required(ticket_dependency_del)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user