Merge pull request #438 from alexbarcelo/autocodestyle

More Code Style --PEP8-centric
This commit is contained in:
Jonathan Barratt 2016-10-28 10:44:55 +07:00 committed by GitHub
commit 44fd60970b
18 changed files with 372 additions and 357 deletions

View File

@ -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

View File

@ -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*.
""" """

View File

@ -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',)

View File

@ -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'):

View File

@ -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']

View File

@ -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")

View File

@ -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.
""" """

View File

@ -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(

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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))

View File

@ -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

View File

@ -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')

View File

@ -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 = {}

View File

@ -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):