forked from extern/django-helpdesk
Merge pull request #438 from alexbarcelo/autocodestyle
More Code Style --PEP8-centric
This commit is contained in:
commit
44fd60970b
@ -20,11 +20,15 @@ install:
|
|||||||
- pip install argparse
|
- pip install argparse
|
||||||
- pip install coverage
|
- pip install coverage
|
||||||
- pip install codecov
|
- pip install codecov
|
||||||
|
- pip install pep8
|
||||||
- pip install -q Django==$DJANGO
|
- pip install -q Django==$DJANGO
|
||||||
- pip install -q -r requirements.txt
|
- pip install -q -r requirements.txt
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- "pep8 --exclude=migrations,south_migrations --ignore=E501 helpdesk"
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- coverage run --source='.' quicktest.py helpdesk
|
- coverage run --source='.' quicktest.py helpdesk
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- codecov
|
- codecov
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
A python interface to the `Akismet <http://akismet.com>`_ API.
|
A python interface to the `Akismet <http://akismet.com>`_ API.
|
||||||
This is a web service for blocking SPAM comments to blogs - or other online
|
This is a web service for blocking SPAM comments to blogs - or other online
|
||||||
services.
|
services.
|
||||||
|
|
||||||
You will need a Wordpress API key, from `wordpress.com <http://wordpress.com>`_.
|
You will need a Wordpress API key, from `wordpress.com <http://wordpress.com>`_.
|
||||||
@ -24,7 +24,7 @@ You should pass in the keyword argument 'agent' to the name of your program,
|
|||||||
when you create an Akismet instance. This sets the ``user-agent`` to a useful
|
when you create an Akismet instance. This sets the ``user-agent`` to a useful
|
||||||
value.
|
value.
|
||||||
|
|
||||||
The default is : ::
|
The default is::
|
||||||
|
|
||||||
Python Interface by Fuzzyman | akismet.py/0.2.0
|
Python Interface by Fuzzyman | akismet.py/0.2.0
|
||||||
|
|
||||||
@ -32,9 +32,9 @@ Whatever you pass in, will replace the *Python Interface by Fuzzyman* part.
|
|||||||
**0.2.0** will change with the version of this interface.
|
**0.2.0** will change with the version of this interface.
|
||||||
|
|
||||||
Usage example::
|
Usage example::
|
||||||
|
|
||||||
from akismet import Akismet
|
from akismet import Akismet
|
||||||
|
|
||||||
api = Akismet(agent='Test Script')
|
api = Akismet(agent='Test Script')
|
||||||
# if apikey.txt is in place,
|
# if apikey.txt is in place,
|
||||||
# the key will automatically be set
|
# the key will automatically be set
|
||||||
@ -70,7 +70,7 @@ __all__ = (
|
|||||||
'Akismet',
|
'Akismet',
|
||||||
'AkismetError',
|
'AkismetError',
|
||||||
'APIKeyError',
|
'APIKeyError',
|
||||||
)
|
)
|
||||||
|
|
||||||
__author__ = 'Michael Foord <fuzzyman AT voidspace DOT org DOT uk>'
|
__author__ = 'Michael Foord <fuzzyman AT voidspace DOT org DOT uk>'
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ if urllib2 is None:
|
|||||||
req = urlfetch.fetch(url=url, payload=data, method=urlfetch.POST, headers=headers)
|
req = urlfetch.fetch(url=url, payload=data, method=urlfetch.POST, headers=headers)
|
||||||
if req.status_code == 200:
|
if req.status_code == 200:
|
||||||
return req.content
|
return req.content
|
||||||
raise Exception('Could not fetch Akismet URL: %s Response code: %s' %
|
raise Exception('Could not fetch Akismet URL: %s Response code: %s' %
|
||||||
(url, req.status_code))
|
(url, req.status_code))
|
||||||
else:
|
else:
|
||||||
def _fetch_url(url, data, headers):
|
def _fetch_url(url, data, headers):
|
||||||
@ -127,7 +127,7 @@ class Akismet(object):
|
|||||||
def _getURL(self):
|
def _getURL(self):
|
||||||
"""
|
"""
|
||||||
Fetch the url to make requests to.
|
Fetch the url to make requests to.
|
||||||
|
|
||||||
This comprises of api key plus the baseurl.
|
This comprises of api key plus the baseurl.
|
||||||
"""
|
"""
|
||||||
return 'http://%s.%s' % (self.key, self.baseurl)
|
return 'http://%s.%s' % (self.key, self.baseurl)
|
||||||
@ -142,11 +142,11 @@ class Akismet(object):
|
|||||||
def setAPIKey(self, key=None, blog_url=None):
|
def setAPIKey(self, key=None, blog_url=None):
|
||||||
"""
|
"""
|
||||||
Set the wordpress API key for all transactions.
|
Set the wordpress API key for all transactions.
|
||||||
|
|
||||||
If you don't specify an explicit API ``key`` and ``blog_url`` it will
|
If you don't specify an explicit API ``key`` and ``blog_url`` it will
|
||||||
attempt to load them from a file called ``apikey.txt`` in the current
|
attempt to load them from a file called ``apikey.txt`` in the current
|
||||||
directory.
|
directory.
|
||||||
|
|
||||||
This method is *usually* called automatically when you create a new
|
This method is *usually* called automatically when you create a new
|
||||||
``Akismet`` instance.
|
``Akismet`` instance.
|
||||||
"""
|
"""
|
||||||
@ -165,14 +165,14 @@ class Akismet(object):
|
|||||||
def verify_key(self):
|
def verify_key(self):
|
||||||
"""
|
"""
|
||||||
This equates to the ``verify-key`` call against the akismet API.
|
This equates to the ``verify-key`` call against the akismet API.
|
||||||
|
|
||||||
It returns ``True`` if the key is valid.
|
It returns ``True`` if the key is valid.
|
||||||
|
|
||||||
The docs state that you *ought* to call this at the start of the
|
The docs state that you *ought* to call this at the start of the
|
||||||
transaction.
|
transaction.
|
||||||
|
|
||||||
It raises ``APIKeyError`` if you have not yet set an API key.
|
It raises ``APIKeyError`` if you have not yet set an API key.
|
||||||
|
|
||||||
If the connection to akismet fails, it allows the normal ``HTTPError``
|
If the connection to akismet fails, it allows the normal ``HTTPError``
|
||||||
or ``URLError`` to be raised.
|
or ``URLError`` to be raised.
|
||||||
(*akismet.py* uses `urllib2 <http://docs.python.org/lib/module-urllib2.html>`_)
|
(*akismet.py* uses `urllib2 <http://docs.python.org/lib/module-urllib2.html>`_)
|
||||||
@ -195,21 +195,21 @@ class Akismet(object):
|
|||||||
"""
|
"""
|
||||||
This function builds the data structure required by ``comment_check``,
|
This function builds the data structure required by ``comment_check``,
|
||||||
``submit_spam``, and ``submit_ham``.
|
``submit_spam``, and ``submit_ham``.
|
||||||
|
|
||||||
It modifies the ``data`` dictionary you give it in place. (and so
|
It modifies the ``data`` dictionary you give it in place. (and so
|
||||||
doesn't return anything)
|
doesn't return anything)
|
||||||
|
|
||||||
It raises an ``AkismetError`` if the user IP or user-agent can't be
|
It raises an ``AkismetError`` if the user IP or user-agent can't be
|
||||||
worked out.
|
worked out.
|
||||||
"""
|
"""
|
||||||
data['comment_content'] = comment
|
data['comment_content'] = comment
|
||||||
if not 'user_ip' in data:
|
if 'user_ip' not in data:
|
||||||
try:
|
try:
|
||||||
val = os.environ['REMOTE_ADDR']
|
val = os.environ['REMOTE_ADDR']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise AkismetError("No 'user_ip' supplied")
|
raise AkismetError("No 'user_ip' supplied")
|
||||||
data['user_ip'] = val
|
data['user_ip'] = val
|
||||||
if not 'user_agent' in data:
|
if 'user_agent' not in data:
|
||||||
try:
|
try:
|
||||||
val = os.environ['HTTP_USER_AGENT']
|
val = os.environ['HTTP_USER_AGENT']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -234,44 +234,44 @@ class Akismet(object):
|
|||||||
def comment_check(self, comment, data=None, build_data=True, DEBUG=False):
|
def comment_check(self, comment, data=None, build_data=True, DEBUG=False):
|
||||||
"""
|
"""
|
||||||
This is the function that checks comments.
|
This is the function that checks comments.
|
||||||
|
|
||||||
It returns ``True`` for spam and ``False`` for ham.
|
It returns ``True`` for spam and ``False`` for ham.
|
||||||
|
|
||||||
If you set ``DEBUG=True`` then it will return the text of the response,
|
If you set ``DEBUG=True`` then it will return the text of the response,
|
||||||
instead of the ``True`` or ``False`` object.
|
instead of the ``True`` or ``False`` object.
|
||||||
|
|
||||||
It raises ``APIKeyError`` if you have not yet set an API key.
|
It raises ``APIKeyError`` if you have not yet set an API key.
|
||||||
|
|
||||||
If the connection to Akismet fails then the ``HTTPError`` or
|
If the connection to Akismet fails then the ``HTTPError`` or
|
||||||
``URLError`` will be propogated.
|
``URLError`` will be propogated.
|
||||||
|
|
||||||
As a minimum it requires the body of the comment. This is the
|
As a minimum it requires the body of the comment. This is the
|
||||||
``comment`` argument.
|
``comment`` argument.
|
||||||
|
|
||||||
Akismet requires some other arguments, and allows some optional ones.
|
Akismet requires some other arguments, and allows some optional ones.
|
||||||
The more information you give it, the more likely it is to be able to
|
The more information you give it, the more likely it is to be able to
|
||||||
make an accurate diagnosise.
|
make an accurate diagnosise.
|
||||||
|
|
||||||
You supply these values using a mapping object (dictionary) as the
|
You supply these values using a mapping object (dictionary) as the
|
||||||
``data`` argument.
|
``data`` argument.
|
||||||
|
|
||||||
If ``build_data`` is ``True`` (the default), then *akismet.py* will
|
If ``build_data`` is ``True`` (the default), then *akismet.py* will
|
||||||
attempt to fill in as much information as possible, using default
|
attempt to fill in as much information as possible, using default
|
||||||
values where necessary. This is particularly useful for programs
|
values where necessary. This is particularly useful for programs
|
||||||
running in a {acro;CGI} environment. A lot of useful information
|
running in a {acro;CGI} environment. A lot of useful information
|
||||||
can be supplied from evironment variables (``os.environ``). See below.
|
can be supplied from evironment variables (``os.environ``). See below.
|
||||||
|
|
||||||
You *only* need supply values for which you don't want defaults filled
|
You *only* need supply values for which you don't want defaults filled
|
||||||
in for. All values must be strings.
|
in for. All values must be strings.
|
||||||
|
|
||||||
There are a few required values. If they are not supplied, and
|
There are a few required values. If they are not supplied, and
|
||||||
defaults can't be worked out, then an ``AkismetError`` is raised.
|
defaults can't be worked out, then an ``AkismetError`` is raised.
|
||||||
|
|
||||||
If you set ``build_data=False`` and a required value is missing an
|
If you set ``build_data=False`` and a required value is missing an
|
||||||
``AkismetError`` will also be raised.
|
``AkismetError`` will also be raised.
|
||||||
|
|
||||||
The normal values (and defaults) are as follows : ::
|
The normal values (and defaults) are as follows : ::
|
||||||
|
|
||||||
'user_ip': os.environ['REMOTE_ADDR'] (*)
|
'user_ip': os.environ['REMOTE_ADDR'] (*)
|
||||||
'user_agent': os.environ['HTTP_USER_AGENT'] (*)
|
'user_agent': os.environ['HTTP_USER_AGENT'] (*)
|
||||||
'referrer': os.environ.get('HTTP_REFERER', 'unknown') [#]_
|
'referrer': os.environ.get('HTTP_REFERER', 'unknown') [#]_
|
||||||
@ -287,16 +287,16 @@ class Akismet(object):
|
|||||||
'SERVER_SIGNATURE': os.environ.get('SERVER_SIGNATURE', '')
|
'SERVER_SIGNATURE': os.environ.get('SERVER_SIGNATURE', '')
|
||||||
'SERVER_SOFTWARE': os.environ.get('SERVER_SOFTWARE', '')
|
'SERVER_SOFTWARE': os.environ.get('SERVER_SOFTWARE', '')
|
||||||
'HTTP_ACCEPT': os.environ.get('HTTP_ACCEPT', '')
|
'HTTP_ACCEPT': os.environ.get('HTTP_ACCEPT', '')
|
||||||
|
|
||||||
(*) Required values
|
(*) Required values
|
||||||
|
|
||||||
You may supply as many additional 'HTTP_*' type values as you wish.
|
You may supply as many additional 'HTTP_*' type values as you wish.
|
||||||
These should correspond to the http headers sent with the request.
|
These should correspond to the http headers sent with the request.
|
||||||
|
|
||||||
.. [#] Note the spelling "referrer". This is a required value by the
|
.. [#] Note the spelling "referrer". This is a required value by the
|
||||||
akismet api - however, referrer information is not always
|
akismet api - however, referrer information is not always
|
||||||
supplied by the browser or server. In fact the HTTP protocol
|
supplied by the browser or server. In fact the HTTP protocol
|
||||||
forbids relying on referrer information for functionality in
|
forbids relying on referrer information for functionality in
|
||||||
programs.
|
programs.
|
||||||
.. [#] The `API docs <http://akismet.com/development/api/>`_ state that this value
|
.. [#] The `API docs <http://akismet.com/development/api/>`_ state that this value
|
||||||
can be " *blank, comment, trackback, pingback, or a made up value*
|
can be " *blank, comment, trackback, pingback, or a made up value*
|
||||||
@ -330,7 +330,7 @@ class Akismet(object):
|
|||||||
"""
|
"""
|
||||||
This function is used to tell akismet that a comment it marked as ham,
|
This function is used to tell akismet that a comment it marked as ham,
|
||||||
is really spam.
|
is really spam.
|
||||||
|
|
||||||
It takes all the same arguments as ``comment_check``, except for
|
It takes all the same arguments as ``comment_check``, except for
|
||||||
*DEBUG*.
|
*DEBUG*.
|
||||||
"""
|
"""
|
||||||
@ -350,7 +350,7 @@ class Akismet(object):
|
|||||||
"""
|
"""
|
||||||
This function is used to tell akismet that a comment it marked as spam,
|
This function is used to tell akismet that a comment it marked as spam,
|
||||||
is really ham.
|
is really ham.
|
||||||
|
|
||||||
It takes all the same arguments as ``comment_check``, except for
|
It takes all the same arguments as ``comment_check``, except for
|
||||||
*DEBUG*.
|
*DEBUG*.
|
||||||
"""
|
"""
|
||||||
|
@ -35,6 +35,7 @@ class CustomFieldMixin(object):
|
|||||||
"""
|
"""
|
||||||
Mixin that provides a method to turn CustomFields into an actual field
|
Mixin that provides a method to turn CustomFields into an actual field
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def customfield_to_field(self, field, instanceargs):
|
def customfield_to_field(self, field, instanceargs):
|
||||||
if field.data_type == 'varchar':
|
if field.data_type == 'varchar':
|
||||||
fieldclass = forms.CharField
|
fieldclass = forms.CharField
|
||||||
@ -76,6 +77,7 @@ class CustomFieldMixin(object):
|
|||||||
|
|
||||||
|
|
||||||
class EditTicketForm(CustomFieldMixin, forms.ModelForm):
|
class EditTicketForm(CustomFieldMixin, forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Ticket
|
model = Ticket
|
||||||
exclude = ('created', 'modified', 'status', 'on_hold', 'resolution', 'last_escalation', 'assigned_to')
|
exclude = ('created', 'modified', 'status', 'on_hold', 'resolution', 'last_escalation', 'assigned_to')
|
||||||
@ -93,11 +95,11 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm):
|
|||||||
except TicketCustomFieldValue.DoesNotExist:
|
except TicketCustomFieldValue.DoesNotExist:
|
||||||
initial_value = None
|
initial_value = None
|
||||||
instanceargs = {
|
instanceargs = {
|
||||||
'label': field.label,
|
'label': field.label,
|
||||||
'help_text': field.help_text,
|
'help_text': field.help_text,
|
||||||
'required': field.required,
|
'required': field.required,
|
||||||
'initial': initial_value,
|
'initial': initial_value,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.customfield_to_field(field, instanceargs)
|
self.customfield_to_field(field, instanceargs)
|
||||||
|
|
||||||
@ -118,6 +120,7 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class EditFollowUpForm(forms.ModelForm):
|
class EditFollowUpForm(forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FollowUp
|
model = FollowUp
|
||||||
exclude = ('date', 'user',)
|
exclude = ('date', 'user',)
|
||||||
@ -133,28 +136,28 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
label=_('Queue'),
|
label=_('Queue'),
|
||||||
required=True,
|
required=True,
|
||||||
choices=()
|
choices=()
|
||||||
)
|
)
|
||||||
|
|
||||||
title = forms.CharField(
|
title = forms.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
required=True,
|
required=True,
|
||||||
widget=forms.TextInput(attrs={'size':'60'}),
|
widget=forms.TextInput(attrs={'size': '60'}),
|
||||||
label=_('Summary of the problem'),
|
label=_('Summary of the problem'),
|
||||||
)
|
)
|
||||||
|
|
||||||
submitter_email = forms.EmailField(
|
submitter_email = forms.EmailField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Submitter E-Mail Address'),
|
label=_('Submitter E-Mail Address'),
|
||||||
widget=forms.TextInput(attrs={'size':'60'}),
|
widget=forms.TextInput(attrs={'size': '60'}),
|
||||||
help_text=_('This e-mail address will receive copies of all public '
|
help_text=_('This e-mail address will receive copies of all public '
|
||||||
'updates to this ticket.'),
|
'updates to this ticket.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
body = forms.CharField(
|
body = forms.CharField(
|
||||||
widget=forms.Textarea(attrs={'cols': 47, 'rows': 15}),
|
widget=forms.Textarea(attrs={'cols': 47, 'rows': 15}),
|
||||||
label=_('Description of Issue'),
|
label=_('Description of Issue'),
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
assigned_to = forms.ChoiceField(
|
assigned_to = forms.ChoiceField(
|
||||||
choices=(),
|
choices=(),
|
||||||
@ -162,7 +165,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
label=_('Case owner'),
|
label=_('Case owner'),
|
||||||
help_text=_('If you select an owner other than yourself, they\'ll be '
|
help_text=_('If you select an owner other than yourself, they\'ll be '
|
||||||
'e-mailed details of this ticket immediately.'),
|
'e-mailed details of this ticket immediately.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
priority = forms.ChoiceField(
|
priority = forms.ChoiceField(
|
||||||
choices=Ticket.PRIORITY_CHOICES,
|
choices=Ticket.PRIORITY_CHOICES,
|
||||||
@ -170,13 +173,13 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
initial='3',
|
initial='3',
|
||||||
label=_('Priority'),
|
label=_('Priority'),
|
||||||
help_text=_('Please select a priority carefully. If unsure, leave it as \'3\'.'),
|
help_text=_('Please select a priority carefully. If unsure, leave it as \'3\'.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
due_date = forms.DateTimeField(
|
due_date = forms.DateTimeField(
|
||||||
widget=extras.SelectDateWidget,
|
widget=extras.SelectDateWidget,
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Due on'),
|
label=_('Due on'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def clean_due_date(self):
|
def clean_due_date(self):
|
||||||
data = self.cleaned_data['due_date']
|
data = self.cleaned_data['due_date']
|
||||||
@ -189,7 +192,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
required=False,
|
required=False,
|
||||||
label=_('Attach File'),
|
label=_('Attach File'),
|
||||||
help_text=_('You can attach a file such as a document or screenshot to this ticket.'),
|
help_text=_('You can attach a file such as a document or screenshot to this ticket.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -198,10 +201,10 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
super(TicketForm, self).__init__(*args, **kwargs)
|
super(TicketForm, self).__init__(*args, **kwargs)
|
||||||
for field in CustomField.objects.all():
|
for field in CustomField.objects.all():
|
||||||
instanceargs = {
|
instanceargs = {
|
||||||
'label': field.label,
|
'label': field.label,
|
||||||
'help_text': field.help_text,
|
'help_text': field.help_text,
|
||||||
'required': field.required,
|
'required': field.required,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.customfield_to_field(field, instanceargs)
|
self.customfield_to_field(field, instanceargs)
|
||||||
|
|
||||||
@ -263,7 +266,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
filename=filename,
|
filename=filename,
|
||||||
mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream',
|
mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream',
|
||||||
size=file.size,
|
size=file.size,
|
||||||
)
|
)
|
||||||
a.file.save(file.name, file, save=False)
|
a.file.save(file.name, file, save=False)
|
||||||
a.save()
|
a.save()
|
||||||
|
|
||||||
@ -288,7 +291,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
sender=q.from_address,
|
sender=q.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(t.submitter_email)
|
messages_sent_to.append(t.submitter_email)
|
||||||
|
|
||||||
if t.assigned_to and \
|
if t.assigned_to and \
|
||||||
@ -303,7 +306,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
sender=q.from_address,
|
sender=q.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(t.assigned_to.email)
|
messages_sent_to.append(t.assigned_to.email)
|
||||||
|
|
||||||
if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to:
|
if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to:
|
||||||
@ -314,7 +317,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
sender=q.from_address,
|
sender=q.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(q.new_ticket_cc)
|
messages_sent_to.append(q.new_ticket_cc)
|
||||||
|
|
||||||
if q.updated_ticket_cc and \
|
if q.updated_ticket_cc and \
|
||||||
@ -327,7 +330,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
sender=q.from_address,
|
sender=q.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
|
|
||||||
return t
|
return t
|
||||||
|
|
||||||
@ -337,20 +340,20 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
label=_('Queue'),
|
label=_('Queue'),
|
||||||
required=True,
|
required=True,
|
||||||
choices=()
|
choices=()
|
||||||
)
|
)
|
||||||
|
|
||||||
title = forms.CharField(
|
title = forms.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
required=True,
|
required=True,
|
||||||
widget=forms.TextInput(),
|
widget=forms.TextInput(),
|
||||||
label=_('Summary of your query'),
|
label=_('Summary of your query'),
|
||||||
)
|
)
|
||||||
|
|
||||||
submitter_email = forms.EmailField(
|
submitter_email = forms.EmailField(
|
||||||
required=True,
|
required=True,
|
||||||
label=_('Your E-Mail Address'),
|
label=_('Your E-Mail Address'),
|
||||||
help_text=_('We will e-mail you when your ticket is updated.'),
|
help_text=_('We will e-mail you when your ticket is updated.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
body = forms.CharField(
|
body = forms.CharField(
|
||||||
widget=forms.Textarea(),
|
widget=forms.Textarea(),
|
||||||
@ -358,7 +361,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
required=True,
|
required=True,
|
||||||
help_text=_('Please be as descriptive as possible, including any '
|
help_text=_('Please be as descriptive as possible, including any '
|
||||||
'details we may need to address your query.'),
|
'details we may need to address your query.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
priority = forms.ChoiceField(
|
priority = forms.ChoiceField(
|
||||||
choices=Ticket.PRIORITY_CHOICES,
|
choices=Ticket.PRIORITY_CHOICES,
|
||||||
@ -366,20 +369,20 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
initial='3',
|
initial='3',
|
||||||
label=_('Urgency'),
|
label=_('Urgency'),
|
||||||
help_text=_('Please select a priority carefully.'),
|
help_text=_('Please select a priority carefully.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
due_date = forms.DateTimeField(
|
due_date = forms.DateTimeField(
|
||||||
widget=extras.SelectDateWidget,
|
widget=extras.SelectDateWidget,
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Due on'),
|
label=_('Due on'),
|
||||||
)
|
)
|
||||||
|
|
||||||
attachment = forms.FileField(
|
attachment = forms.FileField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Attach File'),
|
label=_('Attach File'),
|
||||||
help_text=_('You can attach a file such as a document or screenshot to this ticket.'),
|
help_text=_('You can attach a file such as a document or screenshot to this ticket.'),
|
||||||
max_length=1000,
|
max_length=1000,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -388,10 +391,10 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
super(PublicTicketForm, self).__init__(*args, **kwargs)
|
super(PublicTicketForm, self).__init__(*args, **kwargs)
|
||||||
for field in CustomField.objects.filter(staff_only=False):
|
for field in CustomField.objects.filter(staff_only=False):
|
||||||
instanceargs = {
|
instanceargs = {
|
||||||
'label': field.label,
|
'label': field.label,
|
||||||
'help_text': field.help_text,
|
'help_text': field.help_text,
|
||||||
'required': field.required,
|
'required': field.required,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.customfield_to_field(field, instanceargs)
|
self.customfield_to_field(field, instanceargs)
|
||||||
|
|
||||||
@ -411,7 +414,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
description=self.cleaned_data['body'],
|
description=self.cleaned_data['body'],
|
||||||
priority=self.cleaned_data['priority'],
|
priority=self.cleaned_data['priority'],
|
||||||
due_date=self.cleaned_data['due_date'],
|
due_date=self.cleaned_data['due_date'],
|
||||||
)
|
)
|
||||||
|
|
||||||
if q.default_owner and not t.assigned_to:
|
if q.default_owner and not t.assigned_to:
|
||||||
t.assigned_to = q.default_owner
|
t.assigned_to = q.default_owner
|
||||||
@ -433,7 +436,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
date=timezone.now(),
|
date=timezone.now(),
|
||||||
public=True,
|
public=True,
|
||||||
comment=self.cleaned_data['body'],
|
comment=self.cleaned_data['body'],
|
||||||
)
|
)
|
||||||
|
|
||||||
f.save()
|
f.save()
|
||||||
|
|
||||||
@ -447,7 +450,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
filename=filename,
|
filename=filename,
|
||||||
mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream',
|
mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream',
|
||||||
size=file.size,
|
size=file.size,
|
||||||
)
|
)
|
||||||
a.file.save(file.name, file, save=False)
|
a.file.save(file.name, file, save=False)
|
||||||
a.save()
|
a.save()
|
||||||
|
|
||||||
@ -467,7 +470,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
sender=q.from_address,
|
sender=q.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(t.submitter_email)
|
messages_sent_to.append(t.submitter_email)
|
||||||
|
|
||||||
if t.assigned_to and \
|
if t.assigned_to and \
|
||||||
@ -481,7 +484,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
sender=q.from_address,
|
sender=q.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(t.assigned_to.email)
|
messages_sent_to.append(t.assigned_to.email)
|
||||||
|
|
||||||
if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to:
|
if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to:
|
||||||
@ -492,7 +495,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
sender=q.from_address,
|
sender=q.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(q.new_ticket_cc)
|
messages_sent_to.append(q.new_ticket_cc)
|
||||||
|
|
||||||
if q.updated_ticket_cc and \
|
if q.updated_ticket_cc and \
|
||||||
@ -505,7 +508,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
sender=q.from_address,
|
sender=q.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
|
|
||||||
return t
|
return t
|
||||||
|
|
||||||
@ -515,25 +518,25 @@ class UserSettingsForm(forms.Form):
|
|||||||
label=_('Show Ticket List on Login?'),
|
label=_('Show Ticket List on Login?'),
|
||||||
help_text=_('Display the ticket list upon login? Otherwise, the dashboard is shown.'),
|
help_text=_('Display the ticket list upon login? Otherwise, the dashboard is shown.'),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
email_on_ticket_change = forms.BooleanField(
|
email_on_ticket_change = forms.BooleanField(
|
||||||
label=_('E-mail me on ticket change?'),
|
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?'),
|
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,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
email_on_ticket_assign = forms.BooleanField(
|
email_on_ticket_assign = forms.BooleanField(
|
||||||
label=_('E-mail me when assigned a ticket?'),
|
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?'),
|
help_text=_('If you are assigned a ticket via the web, do you want to receive an e-mail?'),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
email_on_ticket_apichange = forms.BooleanField(
|
email_on_ticket_apichange = forms.BooleanField(
|
||||||
label=_('E-mail me when a ticket is changed via the API?'),
|
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?'),
|
help_text=_('If a ticket is altered by the API, do you want to receive an e-mail?'),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
tickets_per_page = forms.IntegerField(
|
tickets_per_page = forms.IntegerField(
|
||||||
label=_('Number of tickets to show per page'),
|
label=_('Number of tickets to show per page'),
|
||||||
@ -541,22 +544,24 @@ class UserSettingsForm(forms.Form):
|
|||||||
required=False,
|
required=False,
|
||||||
min_value=1,
|
min_value=1,
|
||||||
max_value=1000,
|
max_value=1000,
|
||||||
)
|
)
|
||||||
|
|
||||||
use_email_as_submitter = forms.BooleanField(
|
use_email_as_submitter = forms.BooleanField(
|
||||||
label=_('Use my e-mail address when submitting tickets?'),
|
label=_('Use my e-mail address when submitting tickets?'),
|
||||||
help_text=_('When you submit a ticket, do you want to automatically use your e-mail address as the submitter address? You can type a different e-mail address when entering the ticket if needed, this option only changes the default.'),
|
help_text=_('When you submit a ticket, do you want to automatically use your e-mail address as the submitter address? You can type a different e-mail address when entering the ticket if needed, this option only changes the default.'),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class EmailIgnoreForm(forms.ModelForm):
|
class EmailIgnoreForm(forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IgnoreEmail
|
model = IgnoreEmail
|
||||||
exclude = []
|
exclude = []
|
||||||
|
|
||||||
|
|
||||||
class TicketCCForm(forms.ModelForm):
|
class TicketCCForm(forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TicketCC
|
model = TicketCC
|
||||||
exclude = ('ticket',)
|
exclude = ('ticket',)
|
||||||
@ -571,6 +576,7 @@ class TicketCCForm(forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class TicketDependencyForm(forms.ModelForm):
|
class TicketDependencyForm(forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TicketDependency
|
model = TicketDependency
|
||||||
exclude = ('ticket',)
|
exclude = ('ticket',)
|
||||||
|
@ -109,7 +109,7 @@ def send_templated_mail(template_name,
|
|||||||
|
|
||||||
text_part = template_func(
|
text_part = template_func(
|
||||||
"%s{%% include '%s' %%}" % (t.plain_text, footer_file)
|
"%s{%% include '%s' %%}" % (t.plain_text, footer_file)
|
||||||
).render(context)
|
).render(context)
|
||||||
|
|
||||||
email_html_base_file = os.path.join('helpdesk', locale, 'email_html_base.html')
|
email_html_base_file = os.path.join('helpdesk', locale, 'email_html_base.html')
|
||||||
|
|
||||||
@ -233,8 +233,8 @@ def safe_template_context(ticket):
|
|||||||
|
|
||||||
context = {
|
context = {
|
||||||
'queue': {},
|
'queue': {},
|
||||||
'ticket': {},
|
'ticket': {}
|
||||||
}
|
}
|
||||||
queue = ticket.queue
|
queue = ticket.queue
|
||||||
|
|
||||||
for field in ('title', 'slug', 'email_address', 'from_address', 'locale'):
|
for field in ('title', 'slug', 'email_address', 'from_address', 'locale'):
|
||||||
|
@ -21,6 +21,7 @@ from helpdesk.models import EscalationExclusion, Queue
|
|||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
BaseCommand.__init__(self)
|
BaseCommand.__init__(self)
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ class Command(BaseCommand):
|
|||||||
default=False,
|
default=False,
|
||||||
dest='escalate-verbosely',
|
dest='escalate-verbosely',
|
||||||
help='Display a list of dates excluded'),
|
help='Display a list of dates excluded'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
days = options['days']
|
days = options['days']
|
||||||
|
@ -25,6 +25,7 @@ from helpdesk.models import Queue
|
|||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
BaseCommand.__init__(self)
|
BaseCommand.__init__(self)
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ class Command(BaseCommand):
|
|||||||
make_option(
|
make_option(
|
||||||
'--queues', '-q',
|
'--queues', '-q',
|
||||||
help='Queues to include (default: all). Use queue slugs'),
|
help='Queues to include (default: all). Use queue slugs'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
queue_slugs = options['queues']
|
queue_slugs = options['queues']
|
||||||
@ -71,4 +72,3 @@ class Command(BaseCommand):
|
|||||||
)
|
)
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
self.stdout.write(" .. permission already existed, skipping")
|
self.stdout.write(" .. permission already existed, skipping")
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ django-helpdesk - A Django powered ticket tracker for small enterprise.
|
|||||||
|
|
||||||
See LICENSE for details.
|
See LICENSE for details.
|
||||||
|
|
||||||
create_usersettings.py - Easy way to create helpdesk-specific settings for
|
create_usersettings.py - Easy way to create helpdesk-specific settings for
|
||||||
users who don't yet have them.
|
users who don't yet have them.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ from helpdesk.lib import send_templated_mail, safe_template_context
|
|||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
BaseCommand.__init__(self)
|
BaseCommand.__init__(self)
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ class Command(BaseCommand):
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
help='Display a list of dates excluded'),
|
help='Display a list of dates excluded'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
verbose = False
|
verbose = False
|
||||||
@ -88,17 +89,17 @@ def escalate_tickets(queues, verbose):
|
|||||||
print("Processing: %s" % q)
|
print("Processing: %s" % q)
|
||||||
|
|
||||||
for t in q.ticket_set.filter(
|
for t in q.ticket_set.filter(
|
||||||
Q(status=Ticket.OPEN_STATUS)
|
Q(status=Ticket.OPEN_STATUS) |
|
||||||
| Q(status=Ticket.REOPENED_STATUS)
|
Q(status=Ticket.REOPENED_STATUS)
|
||||||
).exclude(
|
).exclude(
|
||||||
priority=1
|
priority=1
|
||||||
).filter(
|
).filter(
|
||||||
Q(on_hold__isnull=True)
|
Q(on_hold__isnull=True) |
|
||||||
| Q(on_hold=False)
|
Q(on_hold=False)
|
||||||
).filter(
|
).filter(
|
||||||
Q(last_escalation__lte=req_last_escl_date)
|
Q(last_escalation__lte=req_last_escl_date) |
|
||||||
| Q(last_escalation__isnull=True, created__lte=req_last_escl_date)
|
Q(last_escalation__isnull=True, created__lte=req_last_escl_date)
|
||||||
):
|
):
|
||||||
|
|
||||||
t.last_escalation = timezone.now()
|
t.last_escalation = timezone.now()
|
||||||
t.priority -= 1
|
t.priority -= 1
|
||||||
@ -113,7 +114,7 @@ def escalate_tickets(queues, verbose):
|
|||||||
recipients=t.submitter_email,
|
recipients=t.submitter_email,
|
||||||
sender=t.queue.from_address,
|
sender=t.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if t.queue.updated_ticket_cc:
|
if t.queue.updated_ticket_cc:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
@ -122,7 +123,7 @@ def escalate_tickets(queues, verbose):
|
|||||||
recipients=t.queue.updated_ticket_cc,
|
recipients=t.queue.updated_ticket_cc,
|
||||||
sender=t.queue.from_address,
|
sender=t.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if t.assigned_to:
|
if t.assigned_to:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
@ -131,14 +132,14 @@ def escalate_tickets(queues, verbose):
|
|||||||
recipients=t.assigned_to.email,
|
recipients=t.assigned_to.email,
|
||||||
sender=t.queue.from_address,
|
sender=t.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
print(" - Esclating %s from %s>%s" % (
|
print(" - Esclating %s from %s>%s" % (
|
||||||
t.ticket,
|
t.ticket,
|
||||||
t.priority+1,
|
t.priority + 1,
|
||||||
t.priority
|
t.priority
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
f = FollowUp(
|
f = FollowUp(
|
||||||
|
@ -48,7 +48,9 @@ STRIPPED_SUBJECT_STRINGS = [
|
|||||||
"Automatic reply: ",
|
"Automatic reply: ",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
BaseCommand.__init__(self)
|
BaseCommand.__init__(self)
|
||||||
|
|
||||||
@ -58,7 +60,7 @@ class Command(BaseCommand):
|
|||||||
default=False,
|
default=False,
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Hide details about each queue/message as they are processed'),
|
help='Hide details about each queue/message as they are processed'),
|
||||||
)
|
)
|
||||||
|
|
||||||
help = 'Process Jutda Helpdesk queues and process e-mails via ' \
|
help = 'Process Jutda Helpdesk queues and process e-mails via ' \
|
||||||
'POP3/IMAP as required, feeding them into the helpdesk.'
|
'POP3/IMAP as required, feeding them into the helpdesk.'
|
||||||
@ -74,7 +76,7 @@ def process_email(quiet=False):
|
|||||||
allow_email_submission=True):
|
allow_email_submission=True):
|
||||||
|
|
||||||
if not q.email_box_last_check:
|
if not q.email_box_last_check:
|
||||||
q.email_box_last_check = timezone.now()-timedelta(minutes=30)
|
q.email_box_last_check = timezone.now() - timedelta(minutes=30)
|
||||||
|
|
||||||
if not q.email_box_interval:
|
if not q.email_box_interval:
|
||||||
q.email_box_interval = 0
|
q.email_box_interval = 0
|
||||||
@ -176,7 +178,7 @@ def process_queue(q, quiet=False):
|
|||||||
ticket = ticket_from_message(message=data[0][1], queue=q, quiet=quiet)
|
ticket = ticket_from_message(message=data[0][1], queue=q, quiet=quiet)
|
||||||
if ticket:
|
if ticket:
|
||||||
server.store(num, '+FLAGS', '\\Deleted')
|
server.store(num, '+FLAGS', '\\Deleted')
|
||||||
|
|
||||||
server.expunge()
|
server.expunge()
|
||||||
server.close()
|
server.close()
|
||||||
server.logout()
|
server.logout()
|
||||||
@ -221,7 +223,7 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
matchobj = re.match(r".*\["+queue.slug+"-(?P<id>\d+)\]", subject)
|
matchobj = re.match(r".*\[" + queue.slug + "-(?P<id>\d+)\]", subject)
|
||||||
if matchobj:
|
if matchobj:
|
||||||
# This is a reply or forward.
|
# This is a reply or forward.
|
||||||
ticket = matchobj.group('id')
|
ticket = matchobj.group('id')
|
||||||
@ -254,7 +256,7 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
'filename': name,
|
'filename': name,
|
||||||
'content': part.get_payload(decode=True),
|
'content': part.get_payload(decode=True),
|
||||||
'type': part.get_content_type()},
|
'type': part.get_content_type()},
|
||||||
)
|
)
|
||||||
|
|
||||||
counter += 1
|
counter += 1
|
||||||
|
|
||||||
@ -317,7 +319,7 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
if t.status == Ticket.REOPENED_STATUS:
|
if t.status == Ticket.REOPENED_STATUS:
|
||||||
f.new_status = Ticket.REOPENED_STATUS
|
f.new_status = Ticket.REOPENED_STATUS
|
||||||
f.title = _('Ticket Re-Opened by E-Mail Received from %(sender_email)s' % {'sender_email': sender_email})
|
f.title = _('Ticket Re-Opened by E-Mail Received from %(sender_email)s' % {'sender_email': sender_email})
|
||||||
|
|
||||||
f.save()
|
f.save()
|
||||||
|
|
||||||
if not quiet:
|
if not quiet:
|
||||||
@ -332,7 +334,7 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
filename=filename,
|
filename=filename,
|
||||||
mime_type=file['type'],
|
mime_type=file['type'],
|
||||||
size=len(file['content']),
|
size=len(file['content']),
|
||||||
)
|
)
|
||||||
a.file.save(filename, ContentFile(file['content']), save=False)
|
a.file.save(filename, ContentFile(file['content']), save=False)
|
||||||
a.save()
|
a.save()
|
||||||
if not quiet:
|
if not quiet:
|
||||||
@ -349,7 +351,7 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
recipients=sender_email,
|
recipients=sender_email,
|
||||||
sender=queue.from_address,
|
sender=queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if queue.new_ticket_cc:
|
if queue.new_ticket_cc:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
@ -358,7 +360,7 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
recipients=queue.new_ticket_cc,
|
recipients=queue.new_ticket_cc,
|
||||||
sender=queue.from_address,
|
sender=queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc:
|
if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
@ -367,7 +369,7 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
recipients=queue.updated_ticket_cc,
|
recipients=queue.updated_ticket_cc,
|
||||||
sender=queue.from_address,
|
sender=queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
context.update(comment=f.comment)
|
context.update(comment=f.comment)
|
||||||
@ -384,7 +386,7 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
recipients=t.assigned_to.email,
|
recipients=t.assigned_to.email,
|
||||||
sender=queue.from_address,
|
sender=queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if queue.updated_ticket_cc:
|
if queue.updated_ticket_cc:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
@ -393,11 +395,10 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
recipients=queue.updated_ticket_cc,
|
recipients=queue.updated_ticket_cc,
|
||||||
sender=queue.from_address,
|
sender=queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
return t
|
return t
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
process_email()
|
process_email()
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class Queue(models.Model):
|
|||||||
title = models.CharField(
|
title = models.CharField(
|
||||||
_('Title'),
|
_('Title'),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
slug = models.SlugField(
|
slug = models.SlugField(
|
||||||
_('Slug'),
|
_('Slug'),
|
||||||
@ -44,7 +44,7 @@ class Queue(models.Model):
|
|||||||
unique=True,
|
unique=True,
|
||||||
help_text=_('This slug is used when building ticket ID\'s. Once set, '
|
help_text=_('This slug is used when building ticket ID\'s. Once set, '
|
||||||
'try not to change it or e-mailing may get messy.'),
|
'try not to change it or e-mailing may get messy.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
email_address = models.EmailField(
|
email_address = models.EmailField(
|
||||||
_('E-Mail Address'),
|
_('E-Mail Address'),
|
||||||
@ -53,7 +53,7 @@ class Queue(models.Model):
|
|||||||
help_text=_('All outgoing e-mails for this queue will use this e-mail '
|
help_text=_('All outgoing e-mails for this queue will use this e-mail '
|
||||||
'address. If you use IMAP or POP3, this should be the e-mail '
|
'address. If you use IMAP or POP3, this should be the e-mail '
|
||||||
'address for that mailbox.'),
|
'address for that mailbox.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
locale = models.CharField(
|
locale = models.CharField(
|
||||||
_('Locale'),
|
_('Locale'),
|
||||||
@ -62,14 +62,14 @@ class Queue(models.Model):
|
|||||||
null=True,
|
null=True,
|
||||||
help_text=_('Locale of this queue. All correspondence in this '
|
help_text=_('Locale of this queue. All correspondence in this '
|
||||||
'queue will be in this language.'),
|
'queue will be in this language.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
allow_public_submission = models.BooleanField(
|
allow_public_submission = models.BooleanField(
|
||||||
_('Allow Public Submission?'),
|
_('Allow Public Submission?'),
|
||||||
blank=True,
|
blank=True,
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_('Should this queue be listed on the public submission form?'),
|
help_text=_('Should this queue be listed on the public submission form?'),
|
||||||
)
|
)
|
||||||
|
|
||||||
allow_email_submission = models.BooleanField(
|
allow_email_submission = models.BooleanField(
|
||||||
_('Allow E-Mail Submission?'),
|
_('Allow E-Mail Submission?'),
|
||||||
@ -77,7 +77,7 @@ class Queue(models.Model):
|
|||||||
default=False,
|
default=False,
|
||||||
help_text=_('Do you want to poll the e-mail box below for new '
|
help_text=_('Do you want to poll the e-mail box below for new '
|
||||||
'tickets?'),
|
'tickets?'),
|
||||||
)
|
)
|
||||||
|
|
||||||
escalate_days = models.IntegerField(
|
escalate_days = models.IntegerField(
|
||||||
_('Escalation Days'),
|
_('Escalation Days'),
|
||||||
@ -85,7 +85,7 @@ class Queue(models.Model):
|
|||||||
null=True,
|
null=True,
|
||||||
help_text=_('For tickets which are not held, how often do you wish to '
|
help_text=_('For tickets which are not held, how often do you wish to '
|
||||||
'increase their priority? Set to 0 for no escalation.'),
|
'increase their priority? Set to 0 for no escalation.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
new_ticket_cc = models.CharField(
|
new_ticket_cc = models.CharField(
|
||||||
_('New Ticket CC Address'),
|
_('New Ticket CC Address'),
|
||||||
@ -95,7 +95,7 @@ class Queue(models.Model):
|
|||||||
help_text=_('If an e-mail address is entered here, then it will '
|
help_text=_('If an e-mail address is entered here, then it will '
|
||||||
'receive notification of all new tickets created for this queue. '
|
'receive notification of all new tickets created for this queue. '
|
||||||
'Enter a comma between multiple e-mail addresses.'),
|
'Enter a comma between multiple e-mail addresses.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
updated_ticket_cc = models.CharField(
|
updated_ticket_cc = models.CharField(
|
||||||
_('Updated Ticket CC Address'),
|
_('Updated Ticket CC Address'),
|
||||||
@ -106,7 +106,7 @@ class Queue(models.Model):
|
|||||||
'receive notification of all activity (new tickets, closed '
|
'receive notification of all activity (new tickets, closed '
|
||||||
'tickets, updates, reassignments, etc) for this queue. Separate '
|
'tickets, updates, reassignments, etc) for this queue. Separate '
|
||||||
'multiple addresses with a comma.'),
|
'multiple addresses with a comma.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
email_box_type = models.CharField(
|
email_box_type = models.CharField(
|
||||||
_('E-Mail Box Type'),
|
_('E-Mail Box Type'),
|
||||||
@ -116,7 +116,7 @@ class Queue(models.Model):
|
|||||||
null=True,
|
null=True,
|
||||||
help_text=_('E-Mail server type for creating tickets automatically '
|
help_text=_('E-Mail server type for creating tickets automatically '
|
||||||
'from a mailbox - both POP3 and IMAP are supported.'),
|
'from a mailbox - both POP3 and IMAP are supported.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
email_box_host = models.CharField(
|
email_box_host = models.CharField(
|
||||||
_('E-Mail Hostname'),
|
_('E-Mail Hostname'),
|
||||||
@ -125,7 +125,7 @@ class Queue(models.Model):
|
|||||||
null=True,
|
null=True,
|
||||||
help_text=_('Your e-mail server address - either the domain name or '
|
help_text=_('Your e-mail server address - either the domain name or '
|
||||||
'IP address. May be "localhost".'),
|
'IP address. May be "localhost".'),
|
||||||
)
|
)
|
||||||
|
|
||||||
email_box_port = models.IntegerField(
|
email_box_port = models.IntegerField(
|
||||||
_('E-Mail Port'),
|
_('E-Mail Port'),
|
||||||
@ -134,7 +134,7 @@ class Queue(models.Model):
|
|||||||
help_text=_('Port number to use for accessing e-mail. Default for '
|
help_text=_('Port number to use for accessing e-mail. Default for '
|
||||||
'POP3 is "110", and for IMAP is "143". This may differ on some '
|
'POP3 is "110", and for IMAP is "143". This may differ on some '
|
||||||
'servers. Leave it blank to use the defaults.'),
|
'servers. Leave it blank to use the defaults.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
email_box_ssl = models.BooleanField(
|
email_box_ssl = models.BooleanField(
|
||||||
_('Use SSL for E-Mail?'),
|
_('Use SSL for E-Mail?'),
|
||||||
@ -142,7 +142,7 @@ class Queue(models.Model):
|
|||||||
default=False,
|
default=False,
|
||||||
help_text=_('Whether to use SSL for IMAP or POP3 - the default ports '
|
help_text=_('Whether to use SSL for IMAP or POP3 - the default ports '
|
||||||
'when using SSL are 993 for IMAP and 995 for POP3.'),
|
'when using SSL are 993 for IMAP and 995 for POP3.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
email_box_user = models.CharField(
|
email_box_user = models.CharField(
|
||||||
_('E-Mail Username'),
|
_('E-Mail Username'),
|
||||||
@ -150,7 +150,7 @@ class Queue(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_('Username for accessing this mailbox.'),
|
help_text=_('Username for accessing this mailbox.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
email_box_pass = models.CharField(
|
email_box_pass = models.CharField(
|
||||||
_('E-Mail Password'),
|
_('E-Mail Password'),
|
||||||
@ -158,7 +158,7 @@ class Queue(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_('Password for the above username'),
|
help_text=_('Password for the above username'),
|
||||||
)
|
)
|
||||||
|
|
||||||
email_box_imap_folder = models.CharField(
|
email_box_imap_folder = models.CharField(
|
||||||
_('IMAP Folder'),
|
_('IMAP Folder'),
|
||||||
@ -169,7 +169,7 @@ class Queue(models.Model):
|
|||||||
'from? This allows you to use one IMAP account for multiple '
|
'from? This allows you to use one IMAP account for multiple '
|
||||||
'queues, by filtering messages on your IMAP server into separate '
|
'queues, by filtering messages on your IMAP server into separate '
|
||||||
'folders. Default: INBOX.'),
|
'folders. Default: INBOX.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
permission_name = models.CharField(
|
permission_name = models.CharField(
|
||||||
_('Django auth permission name'),
|
_('Django auth permission name'),
|
||||||
@ -178,8 +178,7 @@ class Queue(models.Model):
|
|||||||
null=True,
|
null=True,
|
||||||
editable=False,
|
editable=False,
|
||||||
help_text=_('Name used in the django.contrib.auth permission system'),
|
help_text=_('Name used in the django.contrib.auth permission system'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
email_box_interval = models.IntegerField(
|
email_box_interval = models.IntegerField(
|
||||||
_('E-Mail Check Interval'),
|
_('E-Mail Check Interval'),
|
||||||
@ -187,14 +186,14 @@ class Queue(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
default='5',
|
default='5',
|
||||||
)
|
)
|
||||||
|
|
||||||
email_box_last_check = models.DateTimeField(
|
email_box_last_check = models.DateTimeField(
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
editable=False,
|
editable=False,
|
||||||
# This is updated by management/commands/get_mail.py.
|
# This is updated by management/commands/get_mail.py.
|
||||||
)
|
)
|
||||||
|
|
||||||
socks_proxy_type = models.CharField(
|
socks_proxy_type = models.CharField(
|
||||||
_('Socks Proxy Type'),
|
_('Socks Proxy Type'),
|
||||||
@ -347,24 +346,24 @@ class Ticket(models.Model):
|
|||||||
title = models.CharField(
|
title = models.CharField(
|
||||||
_('Title'),
|
_('Title'),
|
||||||
max_length=200,
|
max_length=200,
|
||||||
)
|
)
|
||||||
|
|
||||||
queue = models.ForeignKey(
|
queue = models.ForeignKey(
|
||||||
Queue,
|
Queue,
|
||||||
verbose_name=_('Queue'),
|
verbose_name=_('Queue'),
|
||||||
)
|
)
|
||||||
|
|
||||||
created = models.DateTimeField(
|
created = models.DateTimeField(
|
||||||
_('Created'),
|
_('Created'),
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Date this ticket was first created'),
|
help_text=_('Date this ticket was first created'),
|
||||||
)
|
)
|
||||||
|
|
||||||
modified = models.DateTimeField(
|
modified = models.DateTimeField(
|
||||||
_('Modified'),
|
_('Modified'),
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Date this ticket was most recently changed.'),
|
help_text=_('Date this ticket was most recently changed.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
submitter_email = models.EmailField(
|
submitter_email = models.EmailField(
|
||||||
_('Submitter E-Mail'),
|
_('Submitter E-Mail'),
|
||||||
@ -372,7 +371,7 @@ class Ticket(models.Model):
|
|||||||
null=True,
|
null=True,
|
||||||
help_text=_('The submitter will receive an email for all public '
|
help_text=_('The submitter will receive an email for all public '
|
||||||
'follow-ups left for this task.'),
|
'follow-ups left for this task.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
assigned_to = models.ForeignKey(
|
assigned_to = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
@ -380,34 +379,34 @@ class Ticket(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_('Assigned to'),
|
verbose_name=_('Assigned to'),
|
||||||
)
|
)
|
||||||
|
|
||||||
status = models.IntegerField(
|
status = models.IntegerField(
|
||||||
_('Status'),
|
_('Status'),
|
||||||
choices=STATUS_CHOICES,
|
choices=STATUS_CHOICES,
|
||||||
default=OPEN_STATUS,
|
default=OPEN_STATUS,
|
||||||
)
|
)
|
||||||
|
|
||||||
on_hold = models.BooleanField(
|
on_hold = models.BooleanField(
|
||||||
_('On Hold'),
|
_('On Hold'),
|
||||||
blank=True,
|
blank=True,
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_('If a ticket is on hold, it will not automatically be escalated.'),
|
help_text=_('If a ticket is on hold, it will not automatically be escalated.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
description = models.TextField(
|
description = models.TextField(
|
||||||
_('Description'),
|
_('Description'),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_('The content of the customers query.'),
|
help_text=_('The content of the customers query.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
resolution = models.TextField(
|
resolution = models.TextField(
|
||||||
_('Resolution'),
|
_('Resolution'),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_('The resolution provided to the customer by our staff.'),
|
help_text=_('The resolution provided to the customer by our staff.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
priority = models.IntegerField(
|
priority = models.IntegerField(
|
||||||
_('Priority'),
|
_('Priority'),
|
||||||
@ -415,13 +414,13 @@ class Ticket(models.Model):
|
|||||||
default=3,
|
default=3,
|
||||||
blank=3,
|
blank=3,
|
||||||
help_text=_('1 = Highest Priority, 5 = Low Priority'),
|
help_text=_('1 = Highest Priority, 5 = Low Priority'),
|
||||||
)
|
)
|
||||||
|
|
||||||
due_date = models.DateTimeField(
|
due_date = models.DateTimeField(
|
||||||
_('Due on'),
|
_('Due on'),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
last_escalation = models.DateTimeField(
|
last_escalation = models.DateTimeField(
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -429,7 +428,7 @@ class Ticket(models.Model):
|
|||||||
editable=False,
|
editable=False,
|
||||||
help_text=_('The date this ticket was last escalated - updated '
|
help_text=_('The date this ticket was last escalated - updated '
|
||||||
'automatically by management/commands/escalate_tickets.py.'),
|
'automatically by management/commands/escalate_tickets.py.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_assigned_to(self):
|
def _get_assigned_to(self):
|
||||||
""" Custom property to allow us to easily print 'Unassigned' if a
|
""" Custom property to allow us to easily print 'Unassigned' if a
|
||||||
@ -479,7 +478,8 @@ class Ticket(models.Model):
|
|||||||
Displays the ticket status, with an "On Hold" message if needed.
|
Displays the ticket status, with an "On Hold" message if needed.
|
||||||
"""
|
"""
|
||||||
held_msg = ''
|
held_msg = ''
|
||||||
if self.on_hold: held_msg = _(' - On Hold')
|
if self.on_hold:
|
||||||
|
held_msg = _(' - On Hold')
|
||||||
dep_msg = ''
|
dep_msg = ''
|
||||||
if not self.can_be_resolved:
|
if not self.can_be_resolved:
|
||||||
dep_msg = _(' - Open dependencies')
|
dep_msg = _(' - Open dependencies')
|
||||||
@ -502,7 +502,7 @@ class Ticket(models.Model):
|
|||||||
reverse('helpdesk_public_view'),
|
reverse('helpdesk_public_view'),
|
||||||
self.ticket_for_url,
|
self.ticket_for_url,
|
||||||
self.submitter_email
|
self.submitter_email
|
||||||
)
|
)
|
||||||
ticket_url = property(_get_ticket_url)
|
ticket_url = property(_get_ticket_url)
|
||||||
|
|
||||||
def _get_staff_url(self):
|
def _get_staff_url(self):
|
||||||
@ -519,8 +519,8 @@ class Ticket(models.Model):
|
|||||||
return u"http://%s%s" % (
|
return u"http://%s%s" % (
|
||||||
site.domain,
|
site.domain,
|
||||||
reverse('helpdesk_view',
|
reverse('helpdesk_view',
|
||||||
args=[self.id])
|
args=[self.id])
|
||||||
)
|
)
|
||||||
staff_url = property(_get_staff_url)
|
staff_url = property(_get_staff_url)
|
||||||
|
|
||||||
def _can_be_resolved(self):
|
def _can_be_resolved(self):
|
||||||
@ -569,6 +569,7 @@ class Ticket(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class FollowUpManager(models.Manager):
|
class FollowUpManager(models.Manager):
|
||||||
|
|
||||||
def private_followups(self):
|
def private_followups(self):
|
||||||
return self.filter(public=False)
|
return self.filter(public=False)
|
||||||
|
|
||||||
@ -593,25 +594,25 @@ class FollowUp(models.Model):
|
|||||||
ticket = models.ForeignKey(
|
ticket = models.ForeignKey(
|
||||||
Ticket,
|
Ticket,
|
||||||
verbose_name=_('Ticket'),
|
verbose_name=_('Ticket'),
|
||||||
)
|
)
|
||||||
|
|
||||||
date = models.DateTimeField(
|
date = models.DateTimeField(
|
||||||
_('Date'),
|
_('Date'),
|
||||||
default = timezone.now
|
default=timezone.now
|
||||||
)
|
)
|
||||||
|
|
||||||
title = models.CharField(
|
title = models.CharField(
|
||||||
_('Title'),
|
_('Title'),
|
||||||
max_length=200,
|
max_length=200,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
comment = models.TextField(
|
comment = models.TextField(
|
||||||
_('Comment'),
|
_('Comment'),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
public = models.BooleanField(
|
public = models.BooleanField(
|
||||||
_('Public'),
|
_('Public'),
|
||||||
@ -619,14 +620,14 @@ class FollowUp(models.Model):
|
|||||||
default=False,
|
default=False,
|
||||||
help_text=_('Public tickets are viewable by the submitter and all '
|
help_text=_('Public tickets are viewable by the submitter and all '
|
||||||
'staff, but non-public tickets can only be seen by staff.'),
|
'staff, but non-public tickets can only be seen by staff.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_('User'),
|
verbose_name=_('User'),
|
||||||
)
|
)
|
||||||
|
|
||||||
new_status = models.IntegerField(
|
new_status = models.IntegerField(
|
||||||
_('New Status'),
|
_('New Status'),
|
||||||
@ -634,12 +635,12 @@ class FollowUp(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_('If the status was changed, what was it changed to?'),
|
help_text=_('If the status was changed, what was it changed to?'),
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = FollowUpManager()
|
objects = FollowUpManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['date']
|
ordering = ('date',)
|
||||||
verbose_name = _('Follow-up')
|
verbose_name = _('Follow-up')
|
||||||
verbose_name_plural = _('Follow-ups')
|
verbose_name_plural = _('Follow-ups')
|
||||||
|
|
||||||
@ -666,24 +667,24 @@ class TicketChange(models.Model):
|
|||||||
followup = models.ForeignKey(
|
followup = models.ForeignKey(
|
||||||
FollowUp,
|
FollowUp,
|
||||||
verbose_name=_('Follow-up'),
|
verbose_name=_('Follow-up'),
|
||||||
)
|
)
|
||||||
|
|
||||||
field = models.CharField(
|
field = models.CharField(
|
||||||
_('Field'),
|
_('Field'),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
old_value = models.TextField(
|
old_value = models.TextField(
|
||||||
_('Old Value'),
|
_('Old Value'),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
new_value = models.TextField(
|
new_value = models.TextField(
|
||||||
_('New Value'),
|
_('New Value'),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
out = '%s ' % self.field
|
out = '%s ' % self.field
|
||||||
@ -695,7 +696,7 @@ class TicketChange(models.Model):
|
|||||||
out += ugettext('changed from "%(old_value)s" to "%(new_value)s"') % {
|
out += ugettext('changed from "%(old_value)s" to "%(new_value)s"') % {
|
||||||
'old_value': self.old_value,
|
'old_value': self.old_value,
|
||||||
'new_value': self.new_value
|
'new_value': self.new_value
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -711,7 +712,7 @@ def attachment_path(instance, filename):
|
|||||||
import os
|
import os
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
os.umask(0)
|
os.umask(0)
|
||||||
path = 'helpdesk/attachments/%s/%s' % (instance.followup.ticket.ticket_for_url, instance.followup.id )
|
path = 'helpdesk/attachments/%s/%s' % (instance.followup.ticket.ticket_for_url, instance.followup.id)
|
||||||
att_path = os.path.join(settings.MEDIA_ROOT, path)
|
att_path = os.path.join(settings.MEDIA_ROOT, path)
|
||||||
if settings.DEFAULT_FILE_STORAGE == "django.core.files.storage.FileSystemStorage":
|
if settings.DEFAULT_FILE_STORAGE == "django.core.files.storage.FileSystemStorage":
|
||||||
if not os.path.exists(att_path):
|
if not os.path.exists(att_path):
|
||||||
@ -729,28 +730,28 @@ class Attachment(models.Model):
|
|||||||
followup = models.ForeignKey(
|
followup = models.ForeignKey(
|
||||||
FollowUp,
|
FollowUp,
|
||||||
verbose_name=_('Follow-up'),
|
verbose_name=_('Follow-up'),
|
||||||
)
|
)
|
||||||
|
|
||||||
file = models.FileField(
|
file = models.FileField(
|
||||||
_('File'),
|
_('File'),
|
||||||
upload_to=attachment_path,
|
upload_to=attachment_path,
|
||||||
max_length=1000,
|
max_length=1000,
|
||||||
)
|
)
|
||||||
|
|
||||||
filename = models.CharField(
|
filename = models.CharField(
|
||||||
_('Filename'),
|
_('Filename'),
|
||||||
max_length=1000,
|
max_length=1000,
|
||||||
)
|
)
|
||||||
|
|
||||||
mime_type = models.CharField(
|
mime_type = models.CharField(
|
||||||
_('MIME Type'),
|
_('MIME Type'),
|
||||||
max_length=255,
|
max_length=255,
|
||||||
)
|
)
|
||||||
|
|
||||||
size = models.IntegerField(
|
size = models.IntegerField(
|
||||||
_('Size'),
|
_('Size'),
|
||||||
help_text=_('Size of this file in bytes'),
|
help_text=_('Size of this file in bytes'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_upload_to(self, field_attname):
|
def get_upload_to(self, field_attname):
|
||||||
""" Get upload_to path specific to this item """
|
""" Get upload_to path specific to this item """
|
||||||
@ -759,13 +760,13 @@ class Attachment(models.Model):
|
|||||||
return u'helpdesk/attachments/%s/%s' % (
|
return u'helpdesk/attachments/%s/%s' % (
|
||||||
self.followup.ticket.ticket_for_url,
|
self.followup.ticket.ticket_for_url,
|
||||||
self.followup.id
|
self.followup.id
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s' % self.filename
|
return '%s' % self.filename
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['filename',]
|
ordering = ('filename',)
|
||||||
verbose_name = _('Attachment')
|
verbose_name = _('Attachment')
|
||||||
verbose_name_plural = _('Attachments')
|
verbose_name_plural = _('Attachments')
|
||||||
|
|
||||||
@ -783,7 +784,7 @@ class PreSetReply(models.Model):
|
|||||||
queue, and the body text is fetched via AJAX.
|
queue, and the body text is fetched via AJAX.
|
||||||
"""
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name', ]
|
ordering = ('name',)
|
||||||
verbose_name = _('Pre-set reply')
|
verbose_name = _('Pre-set reply')
|
||||||
verbose_name_plural = _('Pre-set replies')
|
verbose_name_plural = _('Pre-set replies')
|
||||||
|
|
||||||
@ -792,21 +793,21 @@ class PreSetReply(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Leave blank to allow this reply to be used for all '
|
help_text=_('Leave blank to allow this reply to be used for all '
|
||||||
'queues, or select those queues you wish to limit this reply to.'),
|
'queues, or select those queues you wish to limit this reply to.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
_('Name'),
|
_('Name'),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
help_text=_('Only used to assist users with selecting a reply - not '
|
help_text=_('Only used to assist users with selecting a reply - not '
|
||||||
'shown to the user.'),
|
'shown to the user.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
body = models.TextField(
|
body = models.TextField(
|
||||||
_('Body'),
|
_('Body'),
|
||||||
help_text=_('Context available: {{ ticket }} - ticket object (eg '
|
help_text=_('Context available: {{ ticket }} - ticket object (eg '
|
||||||
'{{ ticket.title }}); {{ queue }} - The queue; and {{ user }} '
|
'{{ ticket.title }}); {{ queue }} - The queue; and {{ user }} '
|
||||||
'- the current user.'),
|
'- the current user.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s' % self.name
|
return '%s' % self.name
|
||||||
@ -829,17 +830,17 @@ class EscalationExclusion(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Leave blank for this exclusion to be applied to all queues, '
|
help_text=_('Leave blank for this exclusion to be applied to all queues, '
|
||||||
'or select those queues you wish to exclude with this entry.'),
|
'or select those queues you wish to exclude with this entry.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
_('Name'),
|
_('Name'),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
date = models.DateField(
|
date = models.DateField(
|
||||||
_('Date'),
|
_('Date'),
|
||||||
help_text=_('Date on which escalation should not happen'),
|
help_text=_('Date on which escalation should not happen'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s' % self.name
|
return '%s' % self.name
|
||||||
@ -862,7 +863,7 @@ class EmailTemplate(models.Model):
|
|||||||
template_name = models.CharField(
|
template_name = models.CharField(
|
||||||
_('Template Name'),
|
_('Template Name'),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
subject = models.CharField(
|
subject = models.CharField(
|
||||||
_('Subject'),
|
_('Subject'),
|
||||||
@ -870,7 +871,7 @@ class EmailTemplate(models.Model):
|
|||||||
help_text=_('This will be prefixed with "[ticket.ticket] ticket.title"'
|
help_text=_('This will be prefixed with "[ticket.ticket] ticket.title"'
|
||||||
'. We recommend something simple such as "(Updated") or "(Closed)"'
|
'. We recommend something simple such as "(Updated") or "(Closed)"'
|
||||||
' - the same context is available as in plain_text, below.'),
|
' - the same context is available as in plain_text, below.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
heading = models.CharField(
|
heading = models.CharField(
|
||||||
_('Heading'),
|
_('Heading'),
|
||||||
@ -878,19 +879,19 @@ class EmailTemplate(models.Model):
|
|||||||
help_text=_('In HTML e-mails, this will be the heading at the top of '
|
help_text=_('In HTML e-mails, this will be the heading at the top of '
|
||||||
'the email - the same context is available as in plain_text, '
|
'the email - the same context is available as in plain_text, '
|
||||||
'below.'),
|
'below.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
plain_text = models.TextField(
|
plain_text = models.TextField(
|
||||||
_('Plain Text'),
|
_('Plain Text'),
|
||||||
help_text=_('The context available to you includes {{ ticket }}, '
|
help_text=_('The context available to you includes {{ ticket }}, '
|
||||||
'{{ queue }}, and depending on the time of the call: '
|
'{{ queue }}, and depending on the time of the call: '
|
||||||
'{{ resolution }} or {{ comment }}.'),
|
'{{ resolution }} or {{ comment }}.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
html = models.TextField(
|
html = models.TextField(
|
||||||
_('HTML'),
|
_('HTML'),
|
||||||
help_text=_('The same context is available here as in plain_text, above.'),
|
help_text=_('The same context is available here as in plain_text, above.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
locale = models.CharField(
|
locale = models.CharField(
|
||||||
_('Locale'),
|
_('Locale'),
|
||||||
@ -898,13 +899,13 @@ class EmailTemplate(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_('Locale of this template.'),
|
help_text=_('Locale of this template.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s' % self.template_name
|
return '%s' % self.template_name
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['template_name', 'locale']
|
ordering = ('template_name', 'locale')
|
||||||
verbose_name = _('e-mail template')
|
verbose_name = _('e-mail template')
|
||||||
verbose_name_plural = _('e-mail templates')
|
verbose_name_plural = _('e-mail templates')
|
||||||
|
|
||||||
@ -919,21 +920,21 @@ class KBCategory(models.Model):
|
|||||||
title = models.CharField(
|
title = models.CharField(
|
||||||
_('Title'),
|
_('Title'),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
slug = models.SlugField(
|
slug = models.SlugField(
|
||||||
_('Slug'),
|
_('Slug'),
|
||||||
)
|
)
|
||||||
|
|
||||||
description = models.TextField(
|
description = models.TextField(
|
||||||
_('Description'),
|
_('Description'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s' % self.title
|
return '%s' % self.title
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['title',]
|
ordering = ('title',)
|
||||||
verbose_name = _('Knowledge base category')
|
verbose_name = _('Knowledge base category')
|
||||||
verbose_name_plural = _('Knowledge base categories')
|
verbose_name_plural = _('Knowledge base categories')
|
||||||
|
|
||||||
@ -951,38 +952,38 @@ class KBItem(models.Model):
|
|||||||
category = models.ForeignKey(
|
category = models.ForeignKey(
|
||||||
KBCategory,
|
KBCategory,
|
||||||
verbose_name=_('Category'),
|
verbose_name=_('Category'),
|
||||||
)
|
)
|
||||||
|
|
||||||
title = models.CharField(
|
title = models.CharField(
|
||||||
_('Title'),
|
_('Title'),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
question = models.TextField(
|
question = models.TextField(
|
||||||
_('Question'),
|
_('Question'),
|
||||||
)
|
)
|
||||||
|
|
||||||
answer = models.TextField(
|
answer = models.TextField(
|
||||||
_('Answer'),
|
_('Answer'),
|
||||||
)
|
)
|
||||||
|
|
||||||
votes = models.IntegerField(
|
votes = models.IntegerField(
|
||||||
_('Votes'),
|
_('Votes'),
|
||||||
help_text=_('Total number of votes cast for this item'),
|
help_text=_('Total number of votes cast for this item'),
|
||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
recommendations = models.IntegerField(
|
recommendations = models.IntegerField(
|
||||||
_('Positive Votes'),
|
_('Positive Votes'),
|
||||||
help_text=_('Number of votes for this item which were POSITIVE.'),
|
help_text=_('Number of votes for this item which were POSITIVE.'),
|
||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
last_updated = models.DateTimeField(
|
last_updated = models.DateTimeField(
|
||||||
_('Last Updated'),
|
_('Last Updated'),
|
||||||
help_text=_('The date on which this question was most recently changed.'),
|
help_text=_('The date on which this question was most recently changed.'),
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if not self.last_updated:
|
if not self.last_updated:
|
||||||
@ -1000,7 +1001,7 @@ class KBItem(models.Model):
|
|||||||
return '%s' % self.title
|
return '%s' % self.title
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['title',]
|
ordering = ('title',)
|
||||||
verbose_name = _('Knowledge base item')
|
verbose_name = _('Knowledge base item')
|
||||||
verbose_name_plural = _('Knowledge base items')
|
verbose_name_plural = _('Knowledge base items')
|
||||||
|
|
||||||
@ -1024,25 +1025,25 @@ class SavedSearch(models.Model):
|
|||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
verbose_name=_('User'),
|
verbose_name=_('User'),
|
||||||
)
|
)
|
||||||
|
|
||||||
title = models.CharField(
|
title = models.CharField(
|
||||||
_('Query Name'),
|
_('Query Name'),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
help_text=_('User-provided name for this query'),
|
help_text=_('User-provided name for this query'),
|
||||||
)
|
)
|
||||||
|
|
||||||
shared = models.BooleanField(
|
shared = models.BooleanField(
|
||||||
_('Shared With Other Users?'),
|
_('Shared With Other Users?'),
|
||||||
blank=True,
|
blank=True,
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_('Should other users see this query?'),
|
help_text=_('Should other users see this query?'),
|
||||||
)
|
)
|
||||||
|
|
||||||
query = models.TextField(
|
query = models.TextField(
|
||||||
_('Search Query'),
|
_('Search Query'),
|
||||||
help_text=_('Pickled query object. Be wary changing this.'),
|
help_text=_('Pickled query object. Be wary changing this.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.shared:
|
if self.shared:
|
||||||
@ -1073,7 +1074,7 @@ class UserSettings(models.Model):
|
|||||||
'Do not change this field via the admin.'),
|
'Do not change this field via the admin.'),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _set_settings(self, data):
|
def _set_settings(self, data):
|
||||||
# data should always be a Python dictionary.
|
# data should always be a Python dictionary.
|
||||||
@ -1138,26 +1139,26 @@ class IgnoreEmail(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Leave blank for this e-mail to be ignored on all queues, '
|
help_text=_('Leave blank for this e-mail to be ignored on all queues, '
|
||||||
'or select those queues you wish to ignore this e-mail for.'),
|
'or select those queues you wish to ignore this e-mail for.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
_('Name'),
|
_('Name'),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
date = models.DateField(
|
date = models.DateField(
|
||||||
_('Date'),
|
_('Date'),
|
||||||
help_text=_('Date on which this e-mail address was added'),
|
help_text=_('Date on which this e-mail address was added'),
|
||||||
blank=True,
|
blank=True,
|
||||||
editable=False
|
editable=False
|
||||||
)
|
)
|
||||||
|
|
||||||
email_address = models.CharField(
|
email_address = models.CharField(
|
||||||
_('E-Mail Address'),
|
_('E-Mail Address'),
|
||||||
max_length=150,
|
max_length=150,
|
||||||
help_text=_('Enter a full e-mail address, or portions with '
|
help_text=_('Enter a full e-mail address, or portions with '
|
||||||
'wildcards, eg *@domain.com or postmaster@*.'),
|
'wildcards, eg *@domain.com or postmaster@*.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
keep_in_mailbox = models.BooleanField(
|
keep_in_mailbox = models.BooleanField(
|
||||||
_('Save Emails in Mailbox?'),
|
_('Save Emails in Mailbox?'),
|
||||||
@ -1165,7 +1166,7 @@ class IgnoreEmail(models.Model):
|
|||||||
default=False,
|
default=False,
|
||||||
help_text=_('Do you want to save emails from this address in the mailbox? '
|
help_text=_('Do you want to save emails from this address in the mailbox? '
|
||||||
'If this is unticked, emails from this address will be deleted.'),
|
'If this is unticked, emails from this address will be deleted.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s' % self.name
|
return '%s' % self.name
|
||||||
@ -1213,7 +1214,7 @@ class TicketCC(models.Model):
|
|||||||
ticket = models.ForeignKey(
|
ticket = models.ForeignKey(
|
||||||
Ticket,
|
Ticket,
|
||||||
verbose_name=_('Ticket'),
|
verbose_name=_('Ticket'),
|
||||||
)
|
)
|
||||||
|
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
@ -1221,28 +1222,28 @@ class TicketCC(models.Model):
|
|||||||
null=True,
|
null=True,
|
||||||
help_text=_('User who wishes to receive updates for this ticket.'),
|
help_text=_('User who wishes to receive updates for this ticket.'),
|
||||||
verbose_name=_('User'),
|
verbose_name=_('User'),
|
||||||
)
|
)
|
||||||
|
|
||||||
email = models.EmailField(
|
email = models.EmailField(
|
||||||
_('E-Mail Address'),
|
_('E-Mail Address'),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_('For non-user followers, enter their e-mail address'),
|
help_text=_('For non-user followers, enter their e-mail address'),
|
||||||
)
|
)
|
||||||
|
|
||||||
can_view = models.BooleanField(
|
can_view = models.BooleanField(
|
||||||
_('Can View Ticket?'),
|
_('Can View Ticket?'),
|
||||||
blank=True,
|
blank=True,
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_('Can this CC login to view the ticket details?'),
|
help_text=_('Can this CC login to view the ticket details?'),
|
||||||
)
|
)
|
||||||
|
|
||||||
can_update = models.BooleanField(
|
can_update = models.BooleanField(
|
||||||
_('Can Update Ticket?'),
|
_('Can Update Ticket?'),
|
||||||
blank=True,
|
blank=True,
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_('Can this CC login and update the ticket?'),
|
help_text=_('Can this CC login and update the ticket?'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _email_address(self):
|
def _email_address(self):
|
||||||
if self.user and self.user.email is not None:
|
if self.user and self.user.email is not None:
|
||||||
@ -1263,6 +1264,7 @@ class TicketCC(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class CustomFieldManager(models.Manager):
|
class CustomFieldManager(models.Manager):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super(CustomFieldManager, self).get_queryset().order_by('ordering')
|
return super(CustomFieldManager, self).get_queryset().order_by('ordering')
|
||||||
|
|
||||||
@ -1278,77 +1280,77 @@ class CustomField(models.Model):
|
|||||||
help_text=_('As used in the database and behind the scenes. '
|
help_text=_('As used in the database and behind the scenes. '
|
||||||
'Must be unique and consist of only lowercase letters with no punctuation.'),
|
'Must be unique and consist of only lowercase letters with no punctuation.'),
|
||||||
unique=True,
|
unique=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
label = models.CharField(
|
label = models.CharField(
|
||||||
_('Label'),
|
_('Label'),
|
||||||
max_length=30,
|
max_length=30,
|
||||||
help_text=_('The display label for this field'),
|
help_text=_('The display label for this field'),
|
||||||
)
|
)
|
||||||
|
|
||||||
help_text = models.TextField(
|
help_text = models.TextField(
|
||||||
_('Help Text'),
|
_('Help Text'),
|
||||||
help_text=_('Shown to the user when editing the ticket'),
|
help_text=_('Shown to the user when editing the ticket'),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
DATA_TYPE_CHOICES = (
|
DATA_TYPE_CHOICES = (
|
||||||
('varchar', _('Character (single line)')),
|
('varchar', _('Character (single line)')),
|
||||||
('text', _('Text (multi-line)')),
|
('text', _('Text (multi-line)')),
|
||||||
('integer', _('Integer')),
|
('integer', _('Integer')),
|
||||||
('decimal', _('Decimal')),
|
('decimal', _('Decimal')),
|
||||||
('list', _('List')),
|
('list', _('List')),
|
||||||
('boolean', _('Boolean (checkbox yes/no)')),
|
('boolean', _('Boolean (checkbox yes/no)')),
|
||||||
('date', _('Date')),
|
('date', _('Date')),
|
||||||
('time', _('Time')),
|
('time', _('Time')),
|
||||||
('datetime', _('Date & Time')),
|
('datetime', _('Date & Time')),
|
||||||
('email', _('E-Mail Address')),
|
('email', _('E-Mail Address')),
|
||||||
('url', _('URL')),
|
('url', _('URL')),
|
||||||
('ipaddress', _('IP Address')),
|
('ipaddress', _('IP Address')),
|
||||||
('slug', _('Slug')),
|
('slug', _('Slug')),
|
||||||
)
|
)
|
||||||
|
|
||||||
data_type = models.CharField(
|
data_type = models.CharField(
|
||||||
_('Data Type'),
|
_('Data Type'),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
help_text=_('Allows you to restrict the data entered into this field'),
|
help_text=_('Allows you to restrict the data entered into this field'),
|
||||||
choices=DATA_TYPE_CHOICES,
|
choices=DATA_TYPE_CHOICES,
|
||||||
)
|
)
|
||||||
|
|
||||||
max_length = models.IntegerField(
|
max_length = models.IntegerField(
|
||||||
_('Maximum Length (characters)'),
|
_('Maximum Length (characters)'),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
decimal_places = models.IntegerField(
|
decimal_places = models.IntegerField(
|
||||||
_('Decimal Places'),
|
_('Decimal Places'),
|
||||||
help_text=_('Only used for decimal fields'),
|
help_text=_('Only used for decimal fields'),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
empty_selection_list = models.BooleanField(
|
empty_selection_list = models.BooleanField(
|
||||||
_('Add empty first choice to List?'),
|
_('Add empty first choice to List?'),
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_('Only for List: adds an empty first entry to the choices list, '
|
help_text=_('Only for List: adds an empty first entry to the choices list, '
|
||||||
'which enforces that the user makes an active choice.'),
|
'which enforces that the user makes an active choice.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
list_values = models.TextField(
|
list_values = models.TextField(
|
||||||
_('List Values'),
|
_('List Values'),
|
||||||
help_text=_('For list fields only. Enter one option per line.'),
|
help_text=_('For list fields only. Enter one option per line.'),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
ordering = models.IntegerField(
|
ordering = models.IntegerField(
|
||||||
_('Ordering'),
|
_('Ordering'),
|
||||||
help_text=_('Lower numbers are displayed first; higher numbers are listed later'),
|
help_text=_('Lower numbers are displayed first; higher numbers are listed later'),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _choices_as_array(self):
|
def _choices_as_array(self):
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
@ -1362,14 +1364,14 @@ class CustomField(models.Model):
|
|||||||
_('Required?'),
|
_('Required?'),
|
||||||
help_text=_('Does the user have to enter a value for this field?'),
|
help_text=_('Does the user have to enter a value for this field?'),
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
staff_only = models.BooleanField(
|
staff_only = models.BooleanField(
|
||||||
_('Staff Only?'),
|
_('Staff Only?'),
|
||||||
help_text=_('If this is ticked, then the public submission form '
|
help_text=_('If this is ticked, then the public submission form '
|
||||||
'will NOT show this field'),
|
'will NOT show this field'),
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = CustomFieldManager()
|
objects = CustomFieldManager()
|
||||||
|
|
||||||
@ -1386,12 +1388,12 @@ class TicketCustomFieldValue(models.Model):
|
|||||||
ticket = models.ForeignKey(
|
ticket = models.ForeignKey(
|
||||||
Ticket,
|
Ticket,
|
||||||
verbose_name=_('Ticket'),
|
verbose_name=_('Ticket'),
|
||||||
)
|
)
|
||||||
|
|
||||||
field = models.ForeignKey(
|
field = models.ForeignKey(
|
||||||
CustomField,
|
CustomField,
|
||||||
verbose_name=_('Field'),
|
verbose_name=_('Field'),
|
||||||
)
|
)
|
||||||
|
|
||||||
value = models.TextField(blank=True, null=True)
|
value = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
@ -1420,13 +1422,13 @@ class TicketDependency(models.Model):
|
|||||||
Ticket,
|
Ticket,
|
||||||
verbose_name=_('Ticket'),
|
verbose_name=_('Ticket'),
|
||||||
related_name='ticketdependency',
|
related_name='ticketdependency',
|
||||||
)
|
)
|
||||||
|
|
||||||
depends_on = models.ForeignKey(
|
depends_on = models.ForeignKey(
|
||||||
Ticket,
|
Ticket,
|
||||||
verbose_name=_('Depends On Ticket'),
|
verbose_name=_('Depends On Ticket'),
|
||||||
related_name='depends_on',
|
related_name='depends_on',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s / %s' % (self.ticket, self.depends_on)
|
return '%s / %s' % (self.ticket, self.depends_on)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
django-helpdesk - A Django powered ticket tracker for small enterprise.
|
django-helpdesk - A Django powered ticket tracker for small enterprise.
|
||||||
|
|
||||||
templatetags/load_helpdesk_settings.py - returns the settings as defined in
|
templatetags/load_helpdesk_settings.py - returns the settings as defined in
|
||||||
django-helpdesk/helpdesk/settings.py
|
django-helpdesk/helpdesk/settings.py
|
||||||
"""
|
"""
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
django-helpdesk - A Django powered ticket tracker for small enterprise.
|
django-helpdesk - A Django powered ticket tracker for small enterprise.
|
||||||
|
|
||||||
templatetags/saved_queries.py - This template tag returns previously saved
|
templatetags/saved_queries.py - This template tag returns previously saved
|
||||||
queries. Therefore you don't need to modify
|
queries. Therefore you don't need to modify
|
||||||
any views.
|
any views.
|
||||||
"""
|
"""
|
||||||
@ -17,8 +17,8 @@ def saved_queries(user):
|
|||||||
return user_saved_queries
|
return user_saved_queries
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import sys
|
import sys
|
||||||
print >> sys.stderr, "'saved_queries' template tag (django-helpdesk) crashed with following error:"
|
print >> sys.stderr, "'saved_queries' template tag (django-helpdesk) crashed with following error:"
|
||||||
print >> sys.stderr, e
|
print >> sys.stderr, e
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
@ -11,6 +11,7 @@ class PublicActionsTestCase(TestCase):
|
|||||||
- Add a followup
|
- Add a followup
|
||||||
- Close resolved case
|
- Close resolved case
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""
|
"""
|
||||||
Create a queue & ticket we can use for later tests.
|
Create a queue & ticket we can use for later tests.
|
||||||
@ -41,26 +42,26 @@ class PublicActionsTestCase(TestCase):
|
|||||||
resolution_text = 'Resolved by test script'
|
resolution_text = 'Resolved by test script'
|
||||||
|
|
||||||
ticket = Ticket.objects.get(id=self.ticket.id)
|
ticket = Ticket.objects.get(id=self.ticket.id)
|
||||||
|
|
||||||
ticket.status = Ticket.RESOLVED_STATUS
|
ticket.status = Ticket.RESOLVED_STATUS
|
||||||
ticket.resolution = resolution_text
|
ticket.resolution = resolution_text
|
||||||
ticket.save()
|
ticket.save()
|
||||||
|
|
||||||
current_followups = ticket.followup_set.all().count()
|
current_followups = ticket.followup_set.all().count()
|
||||||
|
|
||||||
response = self.client.get('%s?ticket=%s&email=%s&close' % (
|
response = self.client.get('%s?ticket=%s&email=%s&close' % (
|
||||||
reverse('helpdesk_public_view'),
|
reverse('helpdesk_public_view'),
|
||||||
ticket.ticket_for_url,
|
ticket.ticket_for_url,
|
||||||
'test.submitter@example.com'))
|
'test.submitter@example.com'))
|
||||||
|
|
||||||
ticket = Ticket.objects.get(id=self.ticket.id)
|
ticket = Ticket.objects.get(id=self.ticket.id)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertTemplateNotUsed(response, 'helpdesk/public_view_form.html')
|
self.assertTemplateNotUsed(response, 'helpdesk/public_view_form.html')
|
||||||
self.assertEqual(ticket.status, Ticket.CLOSED_STATUS)
|
self.assertEqual(ticket.status, Ticket.CLOSED_STATUS)
|
||||||
self.assertEqual(ticket.resolution, resolution_text)
|
self.assertEqual(ticket.resolution, resolution_text)
|
||||||
self.assertEqual(current_followups+1, ticket.followup_set.all().count())
|
self.assertEqual(current_followups + 1, ticket.followup_set.all().count())
|
||||||
|
|
||||||
ticket.resolution = old_resolution
|
ticket.resolution = old_resolution
|
||||||
ticket.status = old_status
|
ticket.status = old_status
|
||||||
ticket.save()
|
ticket.save()
|
||||||
|
@ -48,12 +48,12 @@ class TicketBasicsTestCase(TestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
post_data = {
|
post_data = {
|
||||||
'title': 'Test ticket title',
|
'title': 'Test ticket title',
|
||||||
'queue': self.queue_public.id,
|
'queue': self.queue_public.id,
|
||||||
'submitter_email': 'ticket1.submitter@example.com',
|
'submitter_email': 'ticket1.submitter@example.com',
|
||||||
'body': 'Test ticket body',
|
'body': 'Test ticket body',
|
||||||
'priority': 3,
|
'priority': 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.post(reverse('helpdesk_home'), post_data, follow=True)
|
response = self.client.post(reverse('helpdesk_home'), post_data, follow=True)
|
||||||
last_redirect = response.redirect_chain[-1]
|
last_redirect = response.redirect_chain[-1]
|
||||||
@ -67,7 +67,7 @@ class TicketBasicsTestCase(TestCase):
|
|||||||
self.assertEqual(urlparts.path, reverse('helpdesk_public_view'))
|
self.assertEqual(urlparts.path, reverse('helpdesk_public_view'))
|
||||||
|
|
||||||
# Ensure submitter, new-queue + update-queue were all emailed.
|
# Ensure submitter, new-queue + update-queue were all emailed.
|
||||||
self.assertEqual(email_count+3, len(mail.outbox))
|
self.assertEqual(email_count + 3, len(mail.outbox))
|
||||||
|
|
||||||
def test_create_ticket_private(self):
|
def test_create_ticket_private(self):
|
||||||
email_count = len(mail.outbox)
|
email_count = len(mail.outbox)
|
||||||
@ -114,7 +114,7 @@ class TicketBasicsTestCase(TestCase):
|
|||||||
last_redirect = response.redirect_chain[-1]
|
last_redirect = response.redirect_chain[-1]
|
||||||
last_redirect_url = last_redirect[0]
|
last_redirect_url = last_redirect[0]
|
||||||
# last_redirect_status = last_redirect[1]
|
# last_redirect_status = last_redirect[1]
|
||||||
|
|
||||||
# Ensure we landed on the "View" page.
|
# Ensure we landed on the "View" page.
|
||||||
# Django 1.9 compatible way of testing this
|
# Django 1.9 compatible way of testing this
|
||||||
# https://docs.djangoproject.com/en/1.9/releases/1.9/#http-redirects-no-longer-forced-to-absolute-uris
|
# https://docs.djangoproject.com/en/1.9/releases/1.9/#http-redirects-no-longer-forced-to-absolute-uris
|
||||||
@ -122,4 +122,4 @@ class TicketBasicsTestCase(TestCase):
|
|||||||
self.assertEqual(urlparts.path, reverse('helpdesk_public_view'))
|
self.assertEqual(urlparts.path, reverse('helpdesk_public_view'))
|
||||||
|
|
||||||
# Ensure only two e-mails were sent - submitter & updated.
|
# Ensure only two e-mails were sent - submitter & updated.
|
||||||
self.assertEqual(email_count+2, len(mail.outbox))
|
self.assertEqual(email_count + 2, len(mail.outbox))
|
||||||
|
@ -71,7 +71,7 @@ def api(request, method):
|
|||||||
request.user = authenticate(
|
request.user = authenticate(
|
||||||
username=request.POST.get('user', False),
|
username=request.POST.get('user', False),
|
||||||
password=request.POST.get('password'),
|
password=request.POST.get('password'),
|
||||||
)
|
)
|
||||||
|
|
||||||
if request.user is None:
|
if request.user is None:
|
||||||
return api_return(STATUS_ERROR_PERMISSIONS)
|
return api_return(STATUS_ERROR_PERMISSIONS)
|
||||||
@ -109,6 +109,7 @@ def api_return(status, text='', json=False):
|
|||||||
|
|
||||||
|
|
||||||
class API:
|
class API:
|
||||||
|
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
self.request = request
|
self.request = request
|
||||||
|
|
||||||
@ -195,7 +196,7 @@ class API:
|
|||||||
comment=message,
|
comment=message,
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
title='Comment Added',
|
title='Comment Added',
|
||||||
)
|
)
|
||||||
|
|
||||||
if public:
|
if public:
|
||||||
f.public = True
|
f.public = True
|
||||||
@ -214,7 +215,7 @@ class API:
|
|||||||
recipients=ticket.submitter_email,
|
recipients=ticket.submitter_email,
|
||||||
sender=ticket.queue.from_address,
|
sender=ticket.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(ticket.submitter_email)
|
messages_sent_to.append(ticket.submitter_email)
|
||||||
|
|
||||||
if public:
|
if public:
|
||||||
@ -226,7 +227,7 @@ class API:
|
|||||||
recipients=cc.email_address,
|
recipients=cc.email_address,
|
||||||
sender=ticket.queue.from_address,
|
sender=ticket.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(cc.email_address)
|
messages_sent_to.append(cc.email_address)
|
||||||
|
|
||||||
if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to:
|
if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to:
|
||||||
@ -236,7 +237,7 @@ class API:
|
|||||||
recipients=ticket.queue.updated_ticket_cc,
|
recipients=ticket.queue.updated_ticket_cc,
|
||||||
sender=ticket.queue.from_address,
|
sender=ticket.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(ticket.queue.updated_ticket_cc)
|
messages_sent_to.append(ticket.queue.updated_ticket_cc)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -276,7 +277,7 @@ class API:
|
|||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
title='Resolved',
|
title='Resolved',
|
||||||
public=True,
|
public=True,
|
||||||
)
|
)
|
||||||
f.save()
|
f.save()
|
||||||
|
|
||||||
context = safe_template_context(ticket)
|
context = safe_template_context(ticket)
|
||||||
@ -293,7 +294,7 @@ class API:
|
|||||||
recipients=ticket.submitter_email,
|
recipients=ticket.submitter_email,
|
||||||
sender=ticket.queue.from_address,
|
sender=ticket.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(ticket.submitter_email)
|
messages_sent_to.append(ticket.submitter_email)
|
||||||
|
|
||||||
for cc in ticket.ticketcc_set.all():
|
for cc in ticket.ticketcc_set.all():
|
||||||
@ -304,7 +305,7 @@ class API:
|
|||||||
recipients=cc.email_address,
|
recipients=cc.email_address,
|
||||||
sender=ticket.queue.from_address,
|
sender=ticket.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(cc.email_address)
|
messages_sent_to.append(cc.email_address)
|
||||||
|
|
||||||
if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to:
|
if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to:
|
||||||
@ -314,7 +315,7 @@ class API:
|
|||||||
recipients=ticket.queue.updated_ticket_cc,
|
recipients=ticket.queue.updated_ticket_cc,
|
||||||
sender=ticket.queue.from_address,
|
sender=ticket.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(ticket.queue.updated_ticket_cc)
|
messages_sent_to.append(ticket.queue.updated_ticket_cc)
|
||||||
|
|
||||||
if ticket.assigned_to and \
|
if ticket.assigned_to and \
|
||||||
@ -329,7 +330,7 @@ class API:
|
|||||||
recipients=ticket.assigned_to.email,
|
recipients=ticket.assigned_to.email,
|
||||||
sender=ticket.queue.from_address,
|
sender=ticket.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
ticket.resoltuion = f.comment
|
ticket.resoltuion = f.comment
|
||||||
ticket.status = Ticket.RESOLVED_STATUS
|
ticket.status = Ticket.RESOLVED_STATUS
|
||||||
|
@ -37,22 +37,22 @@ class OpenTicketsByUser(Feed):
|
|||||||
return _("Helpdesk: Open Tickets in queue %(queue)s for %(username)s") % {
|
return _("Helpdesk: Open Tickets in queue %(queue)s for %(username)s") % {
|
||||||
'queue': obj['queue'].title,
|
'queue': obj['queue'].title,
|
||||||
'username': obj['user'].get_username(),
|
'username': obj['user'].get_username(),
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
return _("Helpdesk: Open Tickets for %(username)s") % {
|
return _("Helpdesk: Open Tickets for %(username)s") % {
|
||||||
'username': obj['user'].get_username(),
|
'username': obj['user'].get_username(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def description(self, obj):
|
def description(self, obj):
|
||||||
if obj['queue']:
|
if obj['queue']:
|
||||||
return _("Open and Reopened Tickets in queue %(queue)s for %(username)s") % {
|
return _("Open and Reopened Tickets in queue %(queue)s for %(username)s") % {
|
||||||
'queue': obj['queue'].title,
|
'queue': obj['queue'].title,
|
||||||
'username': obj['user'].get_username(),
|
'username': obj['user'].get_username(),
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
return _("Open and Reopened Tickets for %(username)s") % {
|
return _("Open and Reopened Tickets for %(username)s") % {
|
||||||
'username': obj['user'].get_username(),
|
'username': obj['user'].get_username(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def link(self, obj):
|
def link(self, obj):
|
||||||
if obj['queue']:
|
if obj['queue']:
|
||||||
@ -60,28 +60,28 @@ class OpenTicketsByUser(Feed):
|
|||||||
reverse('helpdesk_list'),
|
reverse('helpdesk_list'),
|
||||||
obj['user'].id,
|
obj['user'].id,
|
||||||
obj['queue'].id,
|
obj['queue'].id,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return u'%s?assigned_to=%s' % (
|
return u'%s?assigned_to=%s' % (
|
||||||
reverse('helpdesk_list'),
|
reverse('helpdesk_list'),
|
||||||
obj['user'].id,
|
obj['user'].id,
|
||||||
)
|
)
|
||||||
|
|
||||||
def items(self, obj):
|
def items(self, obj):
|
||||||
if obj['queue']:
|
if obj['queue']:
|
||||||
return Ticket.objects.filter(
|
return Ticket.objects.filter(
|
||||||
assigned_to=obj['user']
|
assigned_to=obj['user']
|
||||||
).filter(
|
).filter(
|
||||||
queue=obj['queue']
|
queue=obj['queue']
|
||||||
).filter(
|
).filter(
|
||||||
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
|
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return Ticket.objects.filter(
|
return Ticket.objects.filter(
|
||||||
assigned_to=obj['user']
|
assigned_to=obj['user']
|
||||||
).filter(
|
).filter(
|
||||||
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
|
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
|
||||||
)
|
)
|
||||||
|
|
||||||
def item_pubdate(self, item):
|
def item_pubdate(self, item):
|
||||||
return item.created
|
return item.created
|
||||||
@ -103,10 +103,10 @@ class UnassignedTickets(Feed):
|
|||||||
|
|
||||||
def items(self, obj):
|
def items(self, obj):
|
||||||
return Ticket.objects.filter(
|
return Ticket.objects.filter(
|
||||||
assigned_to__isnull=True
|
assigned_to__isnull=True
|
||||||
).filter(
|
).filter(
|
||||||
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
|
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
|
||||||
)
|
)
|
||||||
|
|
||||||
def item_pubdate(self, item):
|
def item_pubdate(self, item):
|
||||||
return item.created
|
return item.created
|
||||||
@ -140,25 +140,25 @@ class OpenTicketsByQueue(Feed):
|
|||||||
def title(self, obj):
|
def title(self, obj):
|
||||||
return _('Helpdesk: Open Tickets in queue %(queue)s') % {
|
return _('Helpdesk: Open Tickets in queue %(queue)s') % {
|
||||||
'queue': obj.title,
|
'queue': obj.title,
|
||||||
}
|
}
|
||||||
|
|
||||||
def description(self, obj):
|
def description(self, obj):
|
||||||
return _('Open and Reopened Tickets in queue %(queue)s') % {
|
return _('Open and Reopened Tickets in queue %(queue)s') % {
|
||||||
'queue': obj.title,
|
'queue': obj.title,
|
||||||
}
|
}
|
||||||
|
|
||||||
def link(self, obj):
|
def link(self, obj):
|
||||||
return '%s?queue=%s' % (
|
return '%s?queue=%s' % (
|
||||||
reverse('helpdesk_list'),
|
reverse('helpdesk_list'),
|
||||||
obj.id,
|
obj.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
def items(self, obj):
|
def items(self, obj):
|
||||||
return Ticket.objects.filter(
|
return Ticket.objects.filter(
|
||||||
queue=obj
|
queue=obj
|
||||||
).filter(
|
).filter(
|
||||||
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
|
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
|
||||||
)
|
)
|
||||||
|
|
||||||
def item_pubdate(self, item):
|
def item_pubdate(self, item):
|
||||||
return item.created
|
return item.created
|
||||||
@ -168,4 +168,3 @@ class OpenTicketsByQueue(Feed):
|
|||||||
return item.assigned_to.get_username()
|
return item.assigned_to.get_username()
|
||||||
else:
|
else:
|
||||||
return _('Unassigned')
|
return _('Unassigned')
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ def homepage(request):
|
|||||||
reverse('helpdesk_public_view'),
|
reverse('helpdesk_public_view'),
|
||||||
ticket.ticket_for_url,
|
ticket.ticket_for_url,
|
||||||
ticket.submitter_email)
|
ticket.submitter_email)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
queue = Queue.objects.get(slug=request.GET.get('queue', None))
|
queue = Queue.objects.get(slug=request.GET.get('queue', None))
|
||||||
@ -67,9 +67,9 @@ def homepage(request):
|
|||||||
knowledgebase_categories = KBCategory.objects.all()
|
knowledgebase_categories = KBCategory.objects.all()
|
||||||
|
|
||||||
return render(request, 'helpdesk/public_homepage.html', {
|
return render(request, 'helpdesk/public_homepage.html', {
|
||||||
'form': form,
|
'form': form,
|
||||||
'helpdesk_settings': helpdesk_settings,
|
'helpdesk_settings': helpdesk_settings,
|
||||||
'kb_categories': knowledgebase_categories
|
'kb_categories': knowledgebase_categories
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ def view_ticket(request):
|
|||||||
'public': 1,
|
'public': 1,
|
||||||
'title': ticket.title,
|
'title': ticket.title,
|
||||||
'comment': _('Submitter accepted resolution and closed ticket'),
|
'comment': _('Submitter accepted resolution and closed ticket'),
|
||||||
}
|
}
|
||||||
if ticket.assigned_to:
|
if ticket.assigned_to:
|
||||||
request.POST['owner'] = ticket.assigned_to.id
|
request.POST['owner'] = ticket.assigned_to.id
|
||||||
request.GET = {}
|
request.GET = {}
|
||||||
|
@ -98,24 +98,24 @@ def dashboard(request):
|
|||||||
|
|
||||||
# open & reopened tickets, assigned to current user
|
# open & reopened tickets, assigned to current user
|
||||||
tickets = Ticket.objects.select_related('queue').filter(
|
tickets = Ticket.objects.select_related('queue').filter(
|
||||||
assigned_to=request.user,
|
assigned_to=request.user,
|
||||||
).exclude(
|
).exclude(
|
||||||
status__in=[Ticket.CLOSED_STATUS, Ticket.RESOLVED_STATUS],
|
status__in=[Ticket.CLOSED_STATUS, Ticket.RESOLVED_STATUS],
|
||||||
)
|
)
|
||||||
|
|
||||||
# closed & resolved tickets, assigned to current user
|
# closed & resolved tickets, assigned to current user
|
||||||
tickets_closed_resolved = Ticket.objects.select_related('queue').filter(
|
tickets_closed_resolved = Ticket.objects.select_related('queue').filter(
|
||||||
assigned_to=request.user,
|
assigned_to=request.user,
|
||||||
status__in=[Ticket.CLOSED_STATUS, Ticket.RESOLVED_STATUS])
|
status__in=[Ticket.CLOSED_STATUS, Ticket.RESOLVED_STATUS])
|
||||||
|
|
||||||
user_queues = _get_user_queues(request.user)
|
user_queues = _get_user_queues(request.user)
|
||||||
|
|
||||||
unassigned_tickets = Ticket.objects.select_related('queue').filter(
|
unassigned_tickets = Ticket.objects.select_related('queue').filter(
|
||||||
assigned_to__isnull=True,
|
assigned_to__isnull=True,
|
||||||
queue__in=user_queues
|
queue__in=user_queues
|
||||||
).exclude(
|
).exclude(
|
||||||
status=Ticket.CLOSED_STATUS,
|
status=Ticket.CLOSED_STATUS,
|
||||||
)
|
)
|
||||||
|
|
||||||
# all tickets, reported by current user
|
# all tickets, reported by current user
|
||||||
all_tickets_reported_by_current_user = ''
|
all_tickets_reported_by_current_user = ''
|
||||||
@ -126,8 +126,8 @@ def dashboard(request):
|
|||||||
).order_by('status')
|
).order_by('status')
|
||||||
|
|
||||||
tickets_in_queues = Ticket.objects.filter(
|
tickets_in_queues = Ticket.objects.filter(
|
||||||
queue__in=user_queues,
|
queue__in=user_queues,
|
||||||
)
|
)
|
||||||
basic_ticket_stats = calc_basic_ticket_stats(tickets_in_queues)
|
basic_ticket_stats = calc_basic_ticket_stats(tickets_in_queues)
|
||||||
|
|
||||||
# The following query builds a grid of queues & ticket statuses,
|
# The following query builds a grid of queues & ticket statuses,
|
||||||
@ -227,7 +227,7 @@ def followup_edit(request, ticket_id, followup_id):
|
|||||||
new_followup.user = followup.user
|
new_followup.user = followup.user
|
||||||
new_followup.save()
|
new_followup.save()
|
||||||
# get list of old attachments & link them to new_followup
|
# get list of old attachments & link them to new_followup
|
||||||
attachments = Attachment.objects.filter(followup = followup)
|
attachments = Attachment.objects.filter(followup=followup)
|
||||||
for attachment in attachments:
|
for attachment in attachments:
|
||||||
attachment.followup = new_followup
|
attachment.followup = new_followup
|
||||||
attachment.save()
|
attachment.save()
|
||||||
@ -290,7 +290,7 @@ def view_ticket(request, ticket_id):
|
|||||||
'owner': owner,
|
'owner': owner,
|
||||||
'title': ticket.title,
|
'title': ticket.title,
|
||||||
'comment': _('Accepted resolution and closed ticket'),
|
'comment': _('Accepted resolution and closed ticket'),
|
||||||
}
|
}
|
||||||
|
|
||||||
return update_ticket(request, ticket_id)
|
return update_ticket(request, ticket_id)
|
||||||
|
|
||||||
@ -299,7 +299,6 @@ def view_ticket(request, ticket_id):
|
|||||||
else:
|
else:
|
||||||
users = User.objects.filter(is_active=True).order_by(User.USERNAME_FIELD)
|
users = User.objects.filter(is_active=True).order_by(User.USERNAME_FIELD)
|
||||||
|
|
||||||
|
|
||||||
# TODO: shouldn't this template get a form to begin with?
|
# TODO: shouldn't this template get a form to begin with?
|
||||||
form = TicketForm(initial={'due_date': ticket.due_date})
|
form = TicketForm(initial={'due_date': ticket.due_date})
|
||||||
|
|
||||||
@ -447,7 +446,7 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
new_user = User.objects.get(id=owner)
|
new_user = User.objects.get(id=owner)
|
||||||
f.title = _('Assigned to %(username)s') % {
|
f.title = _('Assigned to %(username)s') % {
|
||||||
'username': new_user.get_username(),
|
'username': new_user.get_username(),
|
||||||
}
|
}
|
||||||
ticket.assigned_to = new_user
|
ticket.assigned_to = new_user
|
||||||
reassigned = True
|
reassigned = True
|
||||||
# user changed owner to 'unassign'
|
# user changed owner to 'unassign'
|
||||||
@ -482,7 +481,7 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
filename=filename,
|
filename=filename,
|
||||||
mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream',
|
mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream',
|
||||||
size=file.size,
|
size=file.size,
|
||||||
)
|
)
|
||||||
a.file.save(filename, file, save=False)
|
a.file.save(filename, file, save=False)
|
||||||
a.save()
|
a.save()
|
||||||
|
|
||||||
@ -497,7 +496,7 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
field=_('Title'),
|
field=_('Title'),
|
||||||
old_value=ticket.title,
|
old_value=ticket.title,
|
||||||
new_value=title,
|
new_value=title,
|
||||||
)
|
)
|
||||||
c.save()
|
c.save()
|
||||||
ticket.title = title
|
ticket.title = title
|
||||||
|
|
||||||
@ -507,7 +506,7 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
field=_('Priority'),
|
field=_('Priority'),
|
||||||
old_value=ticket.priority,
|
old_value=ticket.priority,
|
||||||
new_value=priority,
|
new_value=priority,
|
||||||
)
|
)
|
||||||
c.save()
|
c.save()
|
||||||
ticket.priority = priority
|
ticket.priority = priority
|
||||||
|
|
||||||
@ -517,11 +516,11 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
field=_('Due on'),
|
field=_('Due on'),
|
||||||
old_value=ticket.due_date,
|
old_value=ticket.due_date,
|
||||||
new_value=due_date,
|
new_value=due_date,
|
||||||
)
|
)
|
||||||
c.save()
|
c.save()
|
||||||
ticket.due_date = due_date
|
ticket.due_date = due_date
|
||||||
|
|
||||||
if new_status in [ Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS ]:
|
if new_status in (Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS):
|
||||||
if new_status == Ticket.RESOLVED_STATUS or ticket.resolution is None:
|
if new_status == Ticket.RESOLVED_STATUS or ticket.resolution is None:
|
||||||
ticket.resolution = comment
|
ticket.resolution = comment
|
||||||
|
|
||||||
@ -533,11 +532,11 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
context.update(
|
context.update(
|
||||||
resolution=ticket.resolution,
|
resolution=ticket.resolution,
|
||||||
comment=f.comment,
|
comment=f.comment,
|
||||||
)
|
)
|
||||||
|
|
||||||
if public and (f.comment or (
|
if public and (f.comment or (
|
||||||
f.new_status in (Ticket.RESOLVED_STATUS,
|
f.new_status in (Ticket.RESOLVED_STATUS,
|
||||||
Ticket.CLOSED_STATUS))):
|
Ticket.CLOSED_STATUS))):
|
||||||
if f.new_status == Ticket.RESOLVED_STATUS:
|
if f.new_status == Ticket.RESOLVED_STATUS:
|
||||||
template = 'resolved_'
|
template = 'resolved_'
|
||||||
elif f.new_status == Ticket.CLOSED_STATUS:
|
elif f.new_status == Ticket.CLOSED_STATUS:
|
||||||
@ -555,7 +554,7 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
sender=ticket.queue.from_address,
|
sender=ticket.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(ticket.submitter_email)
|
messages_sent_to.append(ticket.submitter_email)
|
||||||
|
|
||||||
template_suffix = 'cc'
|
template_suffix = 'cc'
|
||||||
@ -569,7 +568,7 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
sender=ticket.queue.from_address,
|
sender=ticket.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(cc.email_address)
|
messages_sent_to.append(cc.email_address)
|
||||||
|
|
||||||
if ticket.assigned_to and \
|
if ticket.assigned_to and \
|
||||||
@ -602,7 +601,7 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
sender=ticket.queue.from_address,
|
sender=ticket.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(ticket.assigned_to.email)
|
messages_sent_to.append(ticket.assigned_to.email)
|
||||||
|
|
||||||
if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to:
|
if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to:
|
||||||
@ -622,7 +621,7 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
sender=ticket.queue.from_address,
|
sender=ticket.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
|
|
||||||
ticket.save()
|
ticket.save()
|
||||||
|
|
||||||
@ -716,7 +715,7 @@ def mass_update(request):
|
|||||||
recipients=t.submitter_email,
|
recipients=t.submitter_email,
|
||||||
sender=t.queue.from_address,
|
sender=t.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(t.submitter_email)
|
messages_sent_to.append(t.submitter_email)
|
||||||
|
|
||||||
for cc in t.ticketcc_set.all():
|
for cc in t.ticketcc_set.all():
|
||||||
@ -727,7 +726,7 @@ def mass_update(request):
|
|||||||
recipients=cc.email_address,
|
recipients=cc.email_address,
|
||||||
sender=t.queue.from_address,
|
sender=t.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(cc.email_address)
|
messages_sent_to.append(cc.email_address)
|
||||||
|
|
||||||
if t.assigned_to and \
|
if t.assigned_to and \
|
||||||
@ -740,7 +739,7 @@ def mass_update(request):
|
|||||||
recipients=t.assigned_to.email,
|
recipients=t.assigned_to.email,
|
||||||
sender=t.queue.from_address,
|
sender=t.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(t.assigned_to.email)
|
messages_sent_to.append(t.assigned_to.email)
|
||||||
|
|
||||||
if t.queue.updated_ticket_cc and \
|
if t.queue.updated_ticket_cc and \
|
||||||
@ -751,7 +750,7 @@ def mass_update(request):
|
|||||||
recipients=t.queue.updated_ticket_cc,
|
recipients=t.queue.updated_ticket_cc,
|
||||||
sender=t.queue.from_address,
|
sender=t.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
elif action == 'delete':
|
elif action == 'delete':
|
||||||
t.delete()
|
t.delete()
|
||||||
@ -775,7 +774,7 @@ def ticket_list(request):
|
|||||||
'sortreverse': False,
|
'sortreverse': False,
|
||||||
'keyword': None,
|
'keyword': None,
|
||||||
'search_string': None,
|
'search_string': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
from_saved_query = False
|
from_saved_query = False
|
||||||
|
|
||||||
@ -831,13 +830,12 @@ def ticket_list(request):
|
|||||||
# Query deserialization failed. (E.g. was a pickled query)
|
# Query deserialization failed. (E.g. was a pickled query)
|
||||||
return HttpResponseRedirect(reverse('helpdesk_list'))
|
return HttpResponseRedirect(reverse('helpdesk_list'))
|
||||||
|
|
||||||
elif not ('queue' in request.GET
|
elif not ('queue' in request.GET or
|
||||||
or 'assigned_to' in request.GET
|
'assigned_to' in request.GET or
|
||||||
or 'status' in request.GET
|
'status' in request.GET or
|
||||||
or 'q' in request.GET
|
'q' in request.GET or
|
||||||
or 'sort' in request.GET
|
'sort' in request.GET or
|
||||||
or 'sortreverse' in request.GET
|
'sortreverse' in request.GET):
|
||||||
):
|
|
||||||
|
|
||||||
# Fall-back if no querying is being done, force the list to only
|
# Fall-back if no querying is being done, force the list to only
|
||||||
# show open/reopened/resolved (not closed) cases sorted by creation
|
# show open/reopened/resolved (not closed) cases sorted by creation
|
||||||
@ -1113,7 +1111,8 @@ def run_report(request, report):
|
|||||||
# a second table for more complex queries
|
# a second table for more complex queries
|
||||||
summarytable2 = defaultdict(int)
|
summarytable2 = defaultdict(int)
|
||||||
|
|
||||||
month_name = lambda m: MONTHS_3[m].title()
|
def month_name(m):
|
||||||
|
MONTHS_3[m].title()
|
||||||
|
|
||||||
first_ticket = Ticket.objects.all().order_by('created')[0]
|
first_ticket = Ticket.objects.all().order_by('created')[0]
|
||||||
first_month = first_ticket.created.month
|
first_month = first_ticket.created.month
|
||||||
@ -1259,7 +1258,7 @@ run_report = staff_member_required(run_report)
|
|||||||
def save_query(request):
|
def save_query(request):
|
||||||
title = request.POST.get('title', None)
|
title = request.POST.get('title', None)
|
||||||
shared = request.POST.get('shared', False)
|
shared = request.POST.get('shared', False)
|
||||||
if shared == 'on': # django only translates '1', 'true', 't' into True
|
if shared == 'on': # django only translates '1', 'true', 't' into True
|
||||||
shared = True
|
shared = True
|
||||||
query_encoded = request.POST.get('query_encoded', None)
|
query_encoded = request.POST.get('query_encoded', None)
|
||||||
|
|
||||||
@ -1501,7 +1500,7 @@ def days_since_created(today, ticket):
|
|||||||
|
|
||||||
|
|
||||||
def date_rel_to_today(today, offset):
|
def date_rel_to_today(today, offset):
|
||||||
return today - timedelta(days = offset)
|
return today - timedelta(days=offset)
|
||||||
|
|
||||||
|
|
||||||
def sort_string(begin, end):
|
def sort_string(begin, end):
|
||||||
|
Loading…
Reference in New Issue
Block a user