mirror of
https://github.com/django-helpdesk/django-helpdesk.git
synced 2025-06-03 00:15:46 +02:00
772 lines
26 KiB
Python
772 lines
26 KiB
Python
"""
|
|
django-helpdesk - A Django powered ticket tracker for small enterprise.
|
|
|
|
(c) Copyright 2008 Jutda. All Rights Reserved. See LICENSE for details.
|
|
|
|
forms.py - Definitions of newforms-based forms for creating and maintaining
|
|
tickets.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from django import forms
|
|
from django.conf import settings
|
|
from django.contrib.auth import get_user_model
|
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
|
from django.utils import timezone
|
|
from django.utils.translation import gettext_lazy as _
|
|
from helpdesk import settings as helpdesk_settings
|
|
from helpdesk.lib import convert_value, process_attachments, safe_template_context
|
|
from helpdesk.models import (
|
|
Checklist,
|
|
ChecklistTemplate,
|
|
CustomField,
|
|
FollowUp,
|
|
IgnoreEmail,
|
|
Queue,
|
|
Ticket,
|
|
TicketCC,
|
|
TicketCustomFieldValue,
|
|
TicketDependency,
|
|
UserSettings,
|
|
)
|
|
from helpdesk.settings import (
|
|
CUSTOMFIELD_DATE_FORMAT,
|
|
CUSTOMFIELD_DATETIME_FORMAT,
|
|
CUSTOMFIELD_TIME_FORMAT,
|
|
CUSTOMFIELD_TO_FIELD_DICT,
|
|
HELPDESK_SHOW_CUSTOM_FIELDS_FOLLOW_UP_LIST,
|
|
)
|
|
from helpdesk.validators import validate_file_extension
|
|
from helpdesk.signals import new_ticket_done
|
|
import logging
|
|
|
|
|
|
if helpdesk_settings.HELPDESK_KB_ENABLED:
|
|
from helpdesk.models import KBItem
|
|
|
|
logger = logging.getLogger(__name__)
|
|
User = get_user_model()
|
|
|
|
|
|
class CustomFieldMixin(object):
|
|
"""
|
|
Mixin that provides a method to turn CustomFields into an actual field
|
|
"""
|
|
|
|
def customfield_to_field(self, field, instanceargs):
|
|
# Use TextInput widget by default
|
|
instanceargs["widget"] = forms.TextInput(attrs={"class": "form-control"})
|
|
# if-elif branches start with special cases
|
|
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(attrs={"class": "form-control"})
|
|
instanceargs["max_length"] = field.max_length
|
|
elif field.data_type == "integer":
|
|
fieldclass = forms.IntegerField
|
|
instanceargs["widget"] = forms.NumberInput(attrs={"class": "form-control"})
|
|
elif field.data_type == "decimal":
|
|
fieldclass = forms.DecimalField
|
|
instanceargs["decimal_places"] = field.decimal_places
|
|
instanceargs["max_digits"] = field.max_length
|
|
instanceargs["widget"] = forms.NumberInput(attrs={"class": "form-control"})
|
|
elif field.data_type == "list":
|
|
fieldclass = forms.ChoiceField
|
|
instanceargs["choices"] = field.get_choices()
|
|
instanceargs["widget"] = forms.Select(attrs={"class": "form-control"})
|
|
else:
|
|
# Try to use the immediate equivalences dictionary
|
|
try:
|
|
fieldclass = CUSTOMFIELD_TO_FIELD_DICT[field.data_type]
|
|
# Change widgets for the following classes
|
|
if fieldclass == forms.DateField:
|
|
instanceargs["widget"] = forms.DateInput(
|
|
attrs={"class": "form-control date-field"}
|
|
)
|
|
elif fieldclass == forms.DateTimeField:
|
|
instanceargs["widget"] = forms.DateTimeInput(
|
|
attrs={"class": "form-control datetime-field"}
|
|
)
|
|
elif fieldclass == forms.TimeField:
|
|
instanceargs["widget"] = forms.TimeInput(
|
|
attrs={"class": "form-control time-field"}
|
|
)
|
|
elif fieldclass == forms.BooleanField:
|
|
instanceargs["widget"] = forms.CheckboxInput(
|
|
attrs={"class": "form-control"}
|
|
)
|
|
|
|
except KeyError:
|
|
# The data_type was not found anywhere
|
|
raise NameError("Unrecognized data_type %s" % field.data_type)
|
|
|
|
self.fields["custom_%s" % field.name] = fieldclass(**instanceargs)
|
|
|
|
|
|
class EditTicketForm(CustomFieldMixin, forms.ModelForm):
|
|
class Meta:
|
|
model = Ticket
|
|
exclude = (
|
|
"created",
|
|
"modified",
|
|
"status",
|
|
"on_hold",
|
|
"resolution",
|
|
"last_escalation",
|
|
"assigned_to",
|
|
)
|
|
|
|
class Media:
|
|
js = ("helpdesk/js/init_due_date.js", "helpdesk/js/init_datetime_classes.js")
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""
|
|
Add any custom fields that are defined to the form
|
|
"""
|
|
super(EditTicketForm, self).__init__(*args, **kwargs)
|
|
|
|
# Disable and add help_text to the merged_to field on this form
|
|
self.fields["merged_to"].disabled = True
|
|
self.fields["merged_to"].help_text = _(
|
|
"This ticket is merged into the selected ticket."
|
|
)
|
|
|
|
for field in CustomField.objects.all():
|
|
initial_value = None
|
|
try:
|
|
current_value = TicketCustomFieldValue.objects.get(
|
|
ticket=self.instance, field=field
|
|
)
|
|
initial_value = current_value.value
|
|
# Attempt to convert from fixed format string to date/time data
|
|
# type
|
|
if "datetime" == current_value.field.data_type:
|
|
initial_value = datetime.strptime(
|
|
initial_value, CUSTOMFIELD_DATETIME_FORMAT
|
|
)
|
|
elif "date" == current_value.field.data_type:
|
|
initial_value = datetime.strptime(
|
|
initial_value, CUSTOMFIELD_DATE_FORMAT
|
|
)
|
|
elif "time" == current_value.field.data_type:
|
|
initial_value = datetime.strptime(
|
|
initial_value, CUSTOMFIELD_TIME_FORMAT
|
|
)
|
|
# If it is boolean field, transform the value to a real boolean
|
|
# instead of a string
|
|
elif "boolean" == current_value.field.data_type:
|
|
initial_value = "True" == initial_value
|
|
except (TicketCustomFieldValue.DoesNotExist, ValueError, TypeError):
|
|
# ValueError error if parsing fails, using initial_value = current_value.value
|
|
# TypeError if parsing None type
|
|
pass
|
|
instanceargs = {
|
|
"label": field.label,
|
|
"help_text": field.help_text,
|
|
"required": field.required,
|
|
"initial": initial_value,
|
|
}
|
|
|
|
self.customfield_to_field(field, instanceargs)
|
|
|
|
def save(self, *args, **kwargs):
|
|
for field, value in self.cleaned_data.items():
|
|
if field.startswith("custom_"):
|
|
field_name = field.replace("custom_", "", 1)
|
|
customfield = CustomField.objects.get(name=field_name)
|
|
try:
|
|
cfv = TicketCustomFieldValue.objects.get(
|
|
ticket=self.instance, field=customfield
|
|
)
|
|
except ObjectDoesNotExist:
|
|
cfv = TicketCustomFieldValue(
|
|
ticket=self.instance, field=customfield
|
|
)
|
|
|
|
cfv.value = convert_value(value)
|
|
cfv.save()
|
|
|
|
return super(EditTicketForm, self).save(*args, **kwargs)
|
|
|
|
|
|
class EditTicketCustomFieldForm(EditTicketForm):
|
|
"""
|
|
Uses the EditTicketForm logic to provide a form for Ticket custom fields.
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""
|
|
Add any custom fields that are defined to the form
|
|
"""
|
|
super(EditTicketCustomFieldForm, self).__init__(*args, **kwargs)
|
|
|
|
if HELPDESK_SHOW_CUSTOM_FIELDS_FOLLOW_UP_LIST:
|
|
fields = list(self.fields)
|
|
for field in fields:
|
|
if (
|
|
field != "id"
|
|
and field.replace("custom_", "", 1)
|
|
not in HELPDESK_SHOW_CUSTOM_FIELDS_FOLLOW_UP_LIST
|
|
):
|
|
self.fields.pop(field, None)
|
|
|
|
def save(self, *args, **kwargs):
|
|
# if form is saved in a ticket update, it is passed
|
|
# a followup instance to trace custom fields changes
|
|
if "followup" in kwargs:
|
|
followup = kwargs.pop("followup", None)
|
|
|
|
for field, value in self.cleaned_data.items():
|
|
if field.startswith("custom_"):
|
|
if value != self.fields[field].initial:
|
|
followup.ticketchange_set.create(
|
|
field=field.replace("custom_", "", 1),
|
|
old_value=self.fields[field].initial,
|
|
new_value=value,
|
|
)
|
|
|
|
super(EditTicketCustomFieldForm, self).save(*args, **kwargs)
|
|
|
|
class Meta:
|
|
model = Ticket
|
|
fields = (
|
|
"id",
|
|
"merged_to",
|
|
)
|
|
|
|
|
|
class EditFollowUpForm(forms.ModelForm):
|
|
class Meta:
|
|
model = FollowUp
|
|
exclude = (
|
|
"date",
|
|
"user",
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""Filter not opened tickets here."""
|
|
super(EditFollowUpForm, self).__init__(*args, **kwargs)
|
|
self.fields["ticket"].queryset = Ticket.objects.filter(
|
|
status__in=Ticket.OPEN_STATUSES
|
|
)
|
|
|
|
|
|
class AbstractTicketForm(CustomFieldMixin, forms.Form):
|
|
"""
|
|
Contain all the common code and fields between "TicketForm" and
|
|
"PublicTicketForm". This Form is not intended to be used directly.
|
|
"""
|
|
|
|
queue = forms.ChoiceField(
|
|
widget=forms.Select(attrs={"class": "form-control"}),
|
|
label=_("Queue"),
|
|
required=True,
|
|
choices=(),
|
|
)
|
|
|
|
title = forms.CharField(
|
|
max_length=100,
|
|
required=True,
|
|
widget=forms.TextInput(attrs={"class": "form-control"}),
|
|
label=_("Summary of the problem"),
|
|
)
|
|
|
|
body = forms.CharField(
|
|
widget=forms.Textarea(attrs={"class": "form-control"}),
|
|
label=_("Description of your issue"),
|
|
required=True,
|
|
help_text=_("Please be as descriptive as possible and include all details"),
|
|
)
|
|
|
|
priority = forms.ChoiceField(
|
|
widget=forms.Select(attrs={"class": "form-control"}),
|
|
choices=Ticket.PRIORITY_CHOICES,
|
|
required=True,
|
|
initial=getattr(settings, "HELPDESK_PUBLIC_TICKET_PRIORITY", "3"),
|
|
label=_("Priority"),
|
|
help_text=_("Please select a priority carefully. If unsure, leave it as '3'."),
|
|
)
|
|
|
|
due_date = forms.DateTimeField(
|
|
widget=forms.TextInput(attrs={"class": "form-control", "autocomplete": "off"}),
|
|
required=False,
|
|
input_formats=[
|
|
CUSTOMFIELD_DATE_FORMAT,
|
|
CUSTOMFIELD_DATETIME_FORMAT,
|
|
"%d/%m/%Y",
|
|
"%m/%d/%Y",
|
|
"%d.%m.%Y",
|
|
],
|
|
label=_("Due on"),
|
|
)
|
|
|
|
if helpdesk_settings.HELPDESK_ENABLE_ATTACHMENTS:
|
|
attachment = forms.FileField(
|
|
widget=forms.FileInput(attrs={"class": "form-control-file"}),
|
|
required=False,
|
|
label=_("Attach File"),
|
|
help_text=_(
|
|
"You can attach a file to this ticket. "
|
|
"Only file types such as plain text (.txt), "
|
|
"a document (.pdf, .docx, or .odt), "
|
|
"or screenshot (.png or .jpg) may be uploaded."
|
|
),
|
|
validators=[validate_file_extension],
|
|
)
|
|
|
|
class Media:
|
|
js = ("helpdesk/js/init_due_date.js", "helpdesk/js/init_datetime_classes.js")
|
|
|
|
def __init__(self, kbcategory=None, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
if helpdesk_settings.HELPDESK_KB_ENABLED:
|
|
if kbcategory:
|
|
self.fields["kbitem"] = forms.ChoiceField(
|
|
widget=forms.Select(attrs={"class": "form-control"}),
|
|
required=False,
|
|
label=_("Knowledge Base Item"),
|
|
choices=[
|
|
(kbi.pk, kbi.title)
|
|
for kbi in KBItem.objects.filter(
|
|
category=kbcategory.pk, enabled=True
|
|
)
|
|
],
|
|
)
|
|
|
|
def _add_form_custom_fields(self, staff_only_filter=None):
|
|
if staff_only_filter is None:
|
|
queryset = CustomField.objects.all()
|
|
else:
|
|
queryset = CustomField.objects.filter(staff_only=staff_only_filter)
|
|
|
|
for field in queryset:
|
|
instanceargs = {
|
|
"label": field.label,
|
|
"help_text": field.help_text,
|
|
"required": field.required,
|
|
}
|
|
|
|
self.customfield_to_field(field, instanceargs)
|
|
|
|
def _get_queue(self):
|
|
# this procedure is re-defined for public submission form
|
|
return Queue.objects.get(id=int(self.cleaned_data["queue"]))
|
|
|
|
def _create_ticket(self):
|
|
queue = self._get_queue()
|
|
kbitem = None
|
|
if "kbitem" in self.cleaned_data:
|
|
kbitem = KBItem.objects.get(id=int(self.cleaned_data["kbitem"]))
|
|
|
|
ticket = Ticket(
|
|
title=self.cleaned_data["title"],
|
|
submitter_email=self.cleaned_data["submitter_email"],
|
|
created=timezone.now(),
|
|
status=Ticket.OPEN_STATUS,
|
|
queue=queue,
|
|
description=self.cleaned_data["body"],
|
|
priority=self.cleaned_data.get(
|
|
"priority", getattr(settings, "HELPDESK_PUBLIC_TICKET_PRIORITY", "3")
|
|
),
|
|
due_date=self.cleaned_data.get(
|
|
"due_date", getattr(settings, "HELPDESK_PUBLIC_TICKET_DUE_DATE", None)
|
|
)
|
|
or None,
|
|
kbitem=kbitem,
|
|
)
|
|
|
|
return ticket, queue
|
|
|
|
def _create_custom_fields(self, ticket):
|
|
ticket.save_custom_field_values(self.cleaned_data)
|
|
|
|
def _create_follow_up(self, ticket, title, user=None):
|
|
followup = FollowUp(
|
|
ticket=ticket,
|
|
title=title,
|
|
date=timezone.now(),
|
|
public=True,
|
|
comment=self.cleaned_data["body"],
|
|
)
|
|
if user:
|
|
followup.user = user
|
|
return followup
|
|
|
|
def _attach_files_to_follow_up(self, followup):
|
|
files = self.cleaned_data.get("attachment")
|
|
if files:
|
|
files = process_attachments(followup, [files])
|
|
return files
|
|
|
|
@staticmethod
|
|
def _send_messages(ticket, queue, followup, files, user=None):
|
|
context = safe_template_context(ticket)
|
|
context["comment"] = followup.comment
|
|
|
|
roles = {
|
|
"submitter": ("newticket_submitter", context),
|
|
"new_ticket_cc": ("newticket_cc", context),
|
|
"ticket_cc": ("newticket_cc", context),
|
|
}
|
|
if (
|
|
ticket.assigned_to
|
|
and ticket.assigned_to.usersettings_helpdesk.email_on_ticket_assign
|
|
):
|
|
roles["assigned_to"] = ("assigned_owner", context)
|
|
ticket.send(
|
|
roles,
|
|
fail_silently=True,
|
|
files=files,
|
|
)
|
|
|
|
|
|
class TicketForm(AbstractTicketForm):
|
|
"""
|
|
Ticket Form creation for registered users.
|
|
"""
|
|
|
|
submitter_email = forms.EmailField(
|
|
required=False,
|
|
label=_("Submitter E-Mail Address"),
|
|
widget=forms.TextInput(attrs={"class": "form-control", "type": "email"}),
|
|
help_text=_(
|
|
"This e-mail address will receive copies of all public "
|
|
"updates to this ticket."
|
|
),
|
|
)
|
|
assigned_to = forms.ChoiceField(
|
|
widget=(
|
|
forms.Select(attrs={"class": "form-control"})
|
|
if not helpdesk_settings.HELPDESK_CREATE_TICKET_HIDE_ASSIGNED_TO
|
|
else forms.HiddenInput()
|
|
),
|
|
required=False,
|
|
label=_("Case owner"),
|
|
help_text=_(
|
|
"If you select an owner other than yourself, they'll be "
|
|
"e-mailed details of this ticket immediately."
|
|
),
|
|
choices=(),
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""
|
|
Add any custom fields that are defined to the form.
|
|
"""
|
|
queue_choices = kwargs.pop("queue_choices")
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.fields["queue"].choices = queue_choices
|
|
if helpdesk_settings.HELPDESK_STAFF_ONLY_TICKET_OWNERS:
|
|
assignable_users = User.objects.filter(
|
|
is_active=True, is_staff=True
|
|
).order_by(User.USERNAME_FIELD)
|
|
else:
|
|
assignable_users = User.objects.filter(is_active=True).order_by(
|
|
User.USERNAME_FIELD
|
|
)
|
|
self.fields["assigned_to"].choices = [("", "--------")] + [
|
|
(u.id, u.get_username()) for u in assignable_users
|
|
]
|
|
self._add_form_custom_fields()
|
|
|
|
def save(self, user):
|
|
"""
|
|
Writes and returns a Ticket() object
|
|
"""
|
|
|
|
ticket, queue = self._create_ticket()
|
|
if self.cleaned_data["assigned_to"]:
|
|
try:
|
|
u = User.objects.get(id=self.cleaned_data["assigned_to"])
|
|
ticket.assigned_to = u
|
|
except User.DoesNotExist:
|
|
ticket.assigned_to = None
|
|
ticket.save()
|
|
|
|
self._create_custom_fields(ticket)
|
|
|
|
if self.cleaned_data["assigned_to"]:
|
|
title = _("Ticket Opened & Assigned to %(name)s") % {
|
|
"name": ticket.get_assigned_to or _("<invalid user>")
|
|
}
|
|
else:
|
|
title = _("Ticket Opened")
|
|
followup = self._create_follow_up(ticket, title=title, user=user)
|
|
followup.save()
|
|
|
|
if helpdesk_settings.HELPDESK_ENABLE_ATTACHMENTS:
|
|
files = self._attach_files_to_follow_up(followup)
|
|
else:
|
|
files = None
|
|
|
|
# emit signal when the TicketForm.save is done
|
|
new_ticket_done.send(sender="TicketForm", ticket=ticket)
|
|
|
|
self._send_messages(
|
|
ticket=ticket, queue=queue, followup=followup, files=files, user=user
|
|
)
|
|
return ticket
|
|
|
|
|
|
class PublicTicketForm(AbstractTicketForm):
|
|
"""
|
|
Ticket Form creation for all users (public-facing).
|
|
"""
|
|
|
|
submitter_email = forms.EmailField(
|
|
widget=forms.TextInput(attrs={"class": "form-control", "type": "email"}),
|
|
required=True,
|
|
label=_("Your E-Mail Address"),
|
|
help_text=_("We will e-mail you when your ticket is updated."),
|
|
)
|
|
|
|
def __init__(self, hidden_fields=(), readonly_fields=(), *args, **kwargs):
|
|
"""
|
|
Add any (non-staff) custom fields that are defined to the form
|
|
"""
|
|
super(PublicTicketForm, self).__init__(*args, **kwargs)
|
|
self._add_form_custom_fields(False)
|
|
|
|
for field in self.fields.keys():
|
|
if field in hidden_fields:
|
|
self.fields[field].widget = forms.HiddenInput()
|
|
if field in readonly_fields:
|
|
self.fields[field].disabled = True
|
|
|
|
field_deletion_table = {
|
|
"queue": "HELPDESK_PUBLIC_TICKET_QUEUE",
|
|
"priority": "HELPDESK_PUBLIC_TICKET_PRIORITY",
|
|
"due_date": "HELPDESK_PUBLIC_TICKET_DUE_DATE",
|
|
}
|
|
|
|
for field_name, field_setting_key in field_deletion_table.items():
|
|
has_settings_default_value = getattr(settings, field_setting_key, None)
|
|
if has_settings_default_value is not None:
|
|
del self.fields[field_name]
|
|
|
|
public_queues = Queue.objects.filter(allow_public_submission=True)
|
|
|
|
if len(public_queues) == 0:
|
|
logger.warning(
|
|
"There are no public queues defined - public ticket creation is impossible"
|
|
)
|
|
|
|
if "queue" in self.fields:
|
|
self.fields["queue"].choices = [("", "--------")] + [
|
|
(q.id, q.title) for q in public_queues
|
|
]
|
|
|
|
def _get_queue(self):
|
|
if getattr(settings, "HELPDESK_PUBLIC_TICKET_QUEUE", None) is not None:
|
|
# force queue to be the pre-defined one
|
|
# (only for public submissions)
|
|
public_queue = Queue.objects.filter(
|
|
slug=settings.HELPDESK_PUBLIC_TICKET_QUEUE
|
|
).first()
|
|
if not public_queue:
|
|
logger.fatal(
|
|
"Public queue '%s' is configured as default but can't be found",
|
|
settings.HELPDESK_PUBLIC_TICKET_QUEUE,
|
|
)
|
|
return public_queue
|
|
else:
|
|
# get the queue user entered
|
|
return Queue.objects.get(id=int(self.cleaned_data["queue"]))
|
|
|
|
def save(self, user):
|
|
"""
|
|
Writes and returns a Ticket() object
|
|
"""
|
|
ticket, queue = self._create_ticket()
|
|
if queue.default_owner and not ticket.assigned_to:
|
|
ticket.assigned_to = queue.default_owner
|
|
ticket.save()
|
|
|
|
self._create_custom_fields(ticket)
|
|
|
|
followup = self._create_follow_up(
|
|
ticket, title=_("Ticket Opened Via Web"), user=user
|
|
)
|
|
followup.save()
|
|
|
|
files = self._attach_files_to_follow_up(followup)
|
|
|
|
# emit signal when the PublicTicketForm.save is done
|
|
new_ticket_done.send(sender="PublicTicketForm", ticket=ticket)
|
|
|
|
self._send_messages(ticket=ticket, queue=queue, followup=followup, files=files)
|
|
return ticket
|
|
|
|
|
|
class UserSettingsForm(forms.ModelForm):
|
|
class Meta:
|
|
model = UserSettings
|
|
exclude = ["user", "settings_pickled"]
|
|
|
|
|
|
class EmailIgnoreForm(forms.ModelForm):
|
|
class Meta:
|
|
model = IgnoreEmail
|
|
exclude = []
|
|
|
|
|
|
class TicketCCForm(forms.ModelForm):
|
|
"""Adds either an email address or helpdesk user as a CC on a Ticket. Used for processing POST requests."""
|
|
|
|
class Meta:
|
|
model = TicketCC
|
|
exclude = ("ticket",)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(TicketCCForm, self).__init__(*args, **kwargs)
|
|
if helpdesk_settings.HELPDESK_STAFF_ONLY_TICKET_CC:
|
|
users = User.objects.filter(is_active=True, is_staff=True).order_by(
|
|
User.USERNAME_FIELD
|
|
)
|
|
else:
|
|
users = User.objects.filter(is_active=True).order_by(User.USERNAME_FIELD)
|
|
self.fields["user"].queryset = users
|
|
|
|
|
|
class TicketCCUserForm(forms.ModelForm):
|
|
"""Adds a helpdesk user as a CC on a Ticket"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(TicketCCUserForm, self).__init__(*args, **kwargs)
|
|
if helpdesk_settings.HELPDESK_STAFF_ONLY_TICKET_CC:
|
|
users = User.objects.filter(is_active=True, is_staff=True).order_by(
|
|
User.USERNAME_FIELD
|
|
)
|
|
else:
|
|
users = User.objects.filter(is_active=True).order_by(User.USERNAME_FIELD)
|
|
self.fields["user"].queryset = users
|
|
|
|
class Meta:
|
|
model = TicketCC
|
|
exclude = (
|
|
"ticket",
|
|
"email",
|
|
)
|
|
|
|
|
|
class TicketCCEmailForm(forms.ModelForm):
|
|
"""Adds an email address as a CC on a Ticket"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(TicketCCEmailForm, self).__init__(*args, **kwargs)
|
|
|
|
class Meta:
|
|
model = TicketCC
|
|
exclude = (
|
|
"ticket",
|
|
"user",
|
|
)
|
|
|
|
|
|
class TicketDependencyForm(forms.ModelForm):
|
|
"""Adds a different ticket as a dependency for this Ticket"""
|
|
|
|
class Meta:
|
|
model = TicketDependency
|
|
fields = ("depends_on",)
|
|
|
|
def __init__(self, ticket, *args, **kwargs):
|
|
super(TicketDependencyForm, self).__init__(*args, **kwargs)
|
|
|
|
# Only open tickets except myself, existing dependencies and parents
|
|
self.fields["depends_on"].queryset = (
|
|
Ticket.objects.filter(status__in=Ticket.OPEN_STATUSES)
|
|
.exclude(id=ticket.id)
|
|
.exclude(depends_on__ticket=ticket)
|
|
.exclude(ticketdependency__depends_on=ticket)
|
|
)
|
|
|
|
|
|
class TicketResolvesForm(forms.ModelForm):
|
|
"""Adds this ticket as a dependency for a different ticket"""
|
|
|
|
class Meta:
|
|
model = TicketDependency
|
|
fields = ("ticket",)
|
|
|
|
def __init__(self, ticket, *args, **kwargs):
|
|
super(TicketResolvesForm, self).__init__(*args, **kwargs)
|
|
|
|
# Only open tickets except myself, existing dependencies and parents
|
|
self.fields["ticket"].queryset = (
|
|
Ticket.objects.exclude(status__in=Ticket.OPEN_STATUSES)
|
|
.exclude(id=ticket.id)
|
|
.exclude(depends_on__ticket=ticket)
|
|
.exclude(ticketdependency__depends_on=ticket)
|
|
)
|
|
|
|
|
|
class MultipleTicketSelectForm(forms.Form):
|
|
tickets = forms.ModelMultipleChoiceField(
|
|
label=_("Tickets to merge"),
|
|
queryset=Ticket.objects.filter(merged_to=None),
|
|
widget=forms.SelectMultiple(attrs={"class": "form-control"}),
|
|
)
|
|
|
|
def clean_tickets(self):
|
|
tickets = self.cleaned_data.get("tickets")
|
|
if len(tickets) < 2:
|
|
raise ValidationError(_("Please choose at least 2 tickets."))
|
|
if len(tickets) > 4:
|
|
raise ValidationError(_("Impossible to merge more than 4 tickets..."))
|
|
queues = tickets.order_by("queue").distinct().values_list("queue", flat=True)
|
|
if len(queues) != 1:
|
|
raise ValidationError(
|
|
_(
|
|
"All selected tickets must share the same queue in order to be merged."
|
|
)
|
|
)
|
|
return tickets
|
|
|
|
|
|
class ChecklistTemplateForm(forms.ModelForm):
|
|
name = forms.CharField(
|
|
widget=forms.TextInput(attrs={"class": "form-control"}),
|
|
required=True,
|
|
)
|
|
task_list = forms.JSONField(widget=forms.HiddenInput())
|
|
|
|
class Meta:
|
|
model = ChecklistTemplate
|
|
fields = ("name", "task_list")
|
|
|
|
def clean_task_list(self):
|
|
task_list = self.cleaned_data["task_list"]
|
|
return list(map(lambda task: task.strip(), task_list))
|
|
|
|
|
|
class ChecklistForm(forms.ModelForm):
|
|
name = forms.CharField(
|
|
widget=forms.TextInput(attrs={"class": "form-control"}),
|
|
required=True,
|
|
)
|
|
|
|
class Meta:
|
|
model = Checklist
|
|
fields = ("name",)
|
|
|
|
|
|
class CreateChecklistForm(ChecklistForm):
|
|
checklist_template = forms.ModelChoiceField(
|
|
label=_("Template"),
|
|
queryset=ChecklistTemplate.objects.all(),
|
|
widget=forms.Select(attrs={"class": "form-control"}),
|
|
required=False,
|
|
)
|
|
|
|
class Meta(ChecklistForm.Meta):
|
|
fields = ("checklist_template", "name")
|
|
|
|
|
|
class FormControlDeleteFormSet(forms.BaseInlineFormSet):
|
|
deletion_widget = forms.CheckboxInput(attrs={"class": "form-control"})
|