mirror of
https://github.com/django-helpdesk/django-helpdesk.git
synced 2025-02-21 21:00:56 +01:00
Fixing style (according to branch 'autocodestyle') + quicktest
This commit is contained in:
commit
89a6ae01b8
25
.coveragerc
Normal file
25
.coveragerc
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# .coveragerc to control coverage.py
|
||||||
|
# following the example at http://nedbatchelder.com/code/coverage/config.html
|
||||||
|
[run]
|
||||||
|
branch = True
|
||||||
|
include = helpdesk/*
|
||||||
|
omit =
|
||||||
|
*helpdesk/south_migrations/*
|
||||||
|
*helpdesk/migrations/*
|
||||||
|
|
||||||
|
[report]
|
||||||
|
# Regexes for lines to exclude from consideration
|
||||||
|
exclude_lines =
|
||||||
|
# Have to re-enable the standard pragma
|
||||||
|
pragma: no cover
|
||||||
|
|
||||||
|
# Don't complain if tests don't hit defensive assertion code:
|
||||||
|
raise AssertionError
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
# Don't complain if non-runnable code isn't run:
|
||||||
|
if 0:
|
||||||
|
if __name__ == .__main__.:
|
||||||
|
if __name__==.__main__.:
|
||||||
|
|
||||||
|
ignore_errors = True
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,5 +3,6 @@ dist
|
|||||||
django_helpdesk.egg-info
|
django_helpdesk.egg-info
|
||||||
docs/html/*
|
docs/html/*
|
||||||
docs/doctrees/*
|
docs/doctrees/*
|
||||||
|
.coverage
|
||||||
.project
|
.project
|
||||||
.pydevproject
|
.pydevproject
|
378
.pylintrc
Normal file
378
.pylintrc
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
[MASTER]
|
||||||
|
|
||||||
|
# Specify a configuration file.
|
||||||
|
#rcfile=
|
||||||
|
|
||||||
|
# Python code to execute, usually for sys.path manipulation such as
|
||||||
|
# pygtk.require().
|
||||||
|
#init-hook=
|
||||||
|
|
||||||
|
# Add files or directories to the blacklist. They should be base names, not
|
||||||
|
# paths.
|
||||||
|
ignore=CVS,migrations,south_migrations
|
||||||
|
|
||||||
|
# Pickle collected data for later comparisons.
|
||||||
|
persistent=yes
|
||||||
|
|
||||||
|
# List of plugins (as comma separated values of python modules names) to load,
|
||||||
|
# usually to register additional checkers.
|
||||||
|
load-plugins=pylint_django
|
||||||
|
|
||||||
|
# Use multiple processes to speed up Pylint.
|
||||||
|
jobs=1
|
||||||
|
|
||||||
|
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
||||||
|
# active Python interpreter and may run arbitrary code.
|
||||||
|
unsafe-load-any-extension=no
|
||||||
|
|
||||||
|
# A comma-separated list of package or module names from where C extensions may
|
||||||
|
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||||
|
# run arbitrary code
|
||||||
|
extension-pkg-whitelist=
|
||||||
|
|
||||||
|
# Allow optimization of some AST trees. This will activate a peephole AST
|
||||||
|
# optimizer, which will apply various small optimizations. For instance, it can
|
||||||
|
# be used to obtain the result of joining multiple strings with the addition
|
||||||
|
# operator. Joining a lot of strings can lead to a maximum recursion error in
|
||||||
|
# Pylint and this flag can prevent that. It has one side effect, the resulting
|
||||||
|
# AST will be different than the one from reality.
|
||||||
|
optimize-ast=no
|
||||||
|
|
||||||
|
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
|
||||||
|
# Only show warnings with the listed confidence levels. Leave empty to show
|
||||||
|
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
|
||||||
|
confidence=
|
||||||
|
|
||||||
|
# Enable the message, report, category or checker with the given id(s). You can
|
||||||
|
# either give multiple identifier separated by comma (,) or put this option
|
||||||
|
# multiple time. See also the "--disable" option for examples.
|
||||||
|
#enable=
|
||||||
|
|
||||||
|
# Disable the message, report, category or checker with the given id(s). You
|
||||||
|
# can either give multiple identifiers separated by comma (,) or put this
|
||||||
|
# option multiple times (only on the command line, not in the configuration
|
||||||
|
# file where it should appear only once).You can also use "--disable=all" to
|
||||||
|
# disable everything first and then reenable specific checks. For example, if
|
||||||
|
# you want to run only the similarities checker, you can use "--disable=all
|
||||||
|
# --enable=similarities". If you want to run only the classes checker, but have
|
||||||
|
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||||
|
# --disable=W"
|
||||||
|
disable=import-star-module-level,old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating
|
||||||
|
|
||||||
|
|
||||||
|
[REPORTS]
|
||||||
|
|
||||||
|
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||||
|
# (visual studio) and html. You can also give a reporter class, eg
|
||||||
|
# mypackage.mymodule.MyReporterClass.
|
||||||
|
output-format=text
|
||||||
|
|
||||||
|
# Put messages in a separate file for each module / package specified on the
|
||||||
|
# command line instead of printing them on stdout. Reports (if any) will be
|
||||||
|
# written in a file name "pylint_global.[txt|html]".
|
||||||
|
files-output=no
|
||||||
|
|
||||||
|
# Tells whether to display a full report or only the messages
|
||||||
|
reports=yes
|
||||||
|
|
||||||
|
# Python expression which should return a note less than 10 (10 is the highest
|
||||||
|
# note). You have access to the variables errors warning, statement which
|
||||||
|
# respectively contain the number of errors / warnings messages and the total
|
||||||
|
# number of statements analyzed. This is used by the global evaluation report
|
||||||
|
# (RP0004).
|
||||||
|
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||||
|
|
||||||
|
# Template used to display messages. This is a python new-style format string
|
||||||
|
# used to format the message information. See doc for all details
|
||||||
|
#msg-template=
|
||||||
|
|
||||||
|
|
||||||
|
[FORMAT]
|
||||||
|
|
||||||
|
# Maximum number of characters on a single line.
|
||||||
|
max-line-length=120
|
||||||
|
|
||||||
|
# Regexp for a line that is allowed to be longer than the limit.
|
||||||
|
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
||||||
|
|
||||||
|
# Allow the body of an if to be on the same line as the test if there is no
|
||||||
|
# else.
|
||||||
|
single-line-if-stmt=no
|
||||||
|
|
||||||
|
# List of optional constructs for which whitespace checking is disabled. `dict-
|
||||||
|
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
|
||||||
|
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
|
||||||
|
# `empty-line` allows space-only lines.
|
||||||
|
no-space-check=trailing-comma,dict-separator
|
||||||
|
|
||||||
|
# Maximum number of lines in a module
|
||||||
|
max-module-lines=1000
|
||||||
|
|
||||||
|
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||||
|
# tab).
|
||||||
|
indent-string=' '
|
||||||
|
|
||||||
|
# Number of spaces of indent required inside a hanging or continued line.
|
||||||
|
indent-after-paren=4
|
||||||
|
|
||||||
|
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
||||||
|
expected-line-ending-format=
|
||||||
|
|
||||||
|
|
||||||
|
[SPELLING]
|
||||||
|
|
||||||
|
# Spelling dictionary name. Available dictionaries: none. To make it working
|
||||||
|
# install python-enchant package.
|
||||||
|
spelling-dict=
|
||||||
|
|
||||||
|
# List of comma separated words that should not be checked.
|
||||||
|
spelling-ignore-words=
|
||||||
|
|
||||||
|
# A path to a file that contains private dictionary; one word per line.
|
||||||
|
spelling-private-dict-file=
|
||||||
|
|
||||||
|
# Tells whether to store unknown words to indicated private dictionary in
|
||||||
|
# --spelling-private-dict-file option instead of raising a message.
|
||||||
|
spelling-store-unknown-words=no
|
||||||
|
|
||||||
|
|
||||||
|
[SIMILARITIES]
|
||||||
|
|
||||||
|
# Minimum lines number of a similarity.
|
||||||
|
min-similarity-lines=4
|
||||||
|
|
||||||
|
# Ignore comments when computing similarities.
|
||||||
|
ignore-comments=yes
|
||||||
|
|
||||||
|
# Ignore docstrings when computing similarities.
|
||||||
|
ignore-docstrings=yes
|
||||||
|
|
||||||
|
# Ignore imports when computing similarities.
|
||||||
|
ignore-imports=no
|
||||||
|
|
||||||
|
|
||||||
|
[VARIABLES]
|
||||||
|
|
||||||
|
# Tells whether we should check for unused import in __init__ files.
|
||||||
|
init-import=no
|
||||||
|
|
||||||
|
# A regular expression matching the name of dummy variables (i.e. expectedly
|
||||||
|
# not used).
|
||||||
|
dummy-variables-rgx=_$|dummy
|
||||||
|
|
||||||
|
# List of additional names supposed to be defined in builtins. Remember that
|
||||||
|
# you should avoid to define new builtins when possible.
|
||||||
|
additional-builtins=
|
||||||
|
|
||||||
|
# List of strings which can identify a callback function by name. A callback
|
||||||
|
# name must start or end with one of those strings.
|
||||||
|
callbacks=cb_,_cb
|
||||||
|
|
||||||
|
|
||||||
|
[MISCELLANEOUS]
|
||||||
|
|
||||||
|
# List of note tags to take in consideration, separated by a comma.
|
||||||
|
notes=FIXME,XXX,TODO
|
||||||
|
|
||||||
|
|
||||||
|
[TYPECHECK]
|
||||||
|
|
||||||
|
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||||
|
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||||
|
ignore-mixin-members=yes
|
||||||
|
|
||||||
|
# List of module names for which member attributes should not be checked
|
||||||
|
# (useful for modules/projects where namespaces are manipulated during runtime
|
||||||
|
# and thus existing member attributes cannot be deduced by static analysis. It
|
||||||
|
# supports qualified module names, as well as Unix pattern matching.
|
||||||
|
ignored-modules=
|
||||||
|
|
||||||
|
# List of classes names for which member attributes should not be checked
|
||||||
|
# (useful for classes with attributes dynamically set). This supports can work
|
||||||
|
# with qualified names.
|
||||||
|
ignored-classes=
|
||||||
|
|
||||||
|
# List of members which are set dynamically and missed by pylint inference
|
||||||
|
# system, and so shouldn't trigger E1101 when accessed. Python regular
|
||||||
|
# expressions are accepted.
|
||||||
|
generated-members=
|
||||||
|
|
||||||
|
|
||||||
|
[LOGGING]
|
||||||
|
|
||||||
|
# Logging modules to check that the string format arguments are in logging
|
||||||
|
# function parameter format
|
||||||
|
logging-modules=logging
|
||||||
|
|
||||||
|
|
||||||
|
[BASIC]
|
||||||
|
|
||||||
|
# List of builtins function names that should not be used, separated by a comma
|
||||||
|
bad-functions=map,filter,input
|
||||||
|
|
||||||
|
# Good variable names which should always be accepted, separated by a comma
|
||||||
|
good-names=i,j,k,ex,Run,_
|
||||||
|
|
||||||
|
# Bad variable names which should always be refused, separated by a comma
|
||||||
|
bad-names=foo,bar,baz,toto,tutu,tata
|
||||||
|
|
||||||
|
# Colon-delimited sets of names that determine each other's naming style when
|
||||||
|
# the name regexes allow several styles.
|
||||||
|
name-group=
|
||||||
|
|
||||||
|
# Include a hint for the correct naming format with invalid-name
|
||||||
|
include-naming-hint=no
|
||||||
|
|
||||||
|
# Regular expression matching correct function names
|
||||||
|
function-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||||
|
|
||||||
|
# Naming hint for function names
|
||||||
|
function-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||||
|
|
||||||
|
# Regular expression matching correct variable names
|
||||||
|
variable-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||||
|
|
||||||
|
# Naming hint for variable names
|
||||||
|
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||||
|
|
||||||
|
# Regular expression matching correct constant names
|
||||||
|
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
||||||
|
|
||||||
|
# Naming hint for constant names
|
||||||
|
const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
||||||
|
|
||||||
|
# Regular expression matching correct attribute names
|
||||||
|
attr-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||||
|
|
||||||
|
# Naming hint for attribute names
|
||||||
|
attr-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||||
|
|
||||||
|
# Regular expression matching correct argument names
|
||||||
|
argument-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||||
|
|
||||||
|
# Naming hint for argument names
|
||||||
|
argument-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||||
|
|
||||||
|
# Regular expression matching correct class attribute names
|
||||||
|
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
||||||
|
|
||||||
|
# Naming hint for class attribute names
|
||||||
|
class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
||||||
|
|
||||||
|
# Regular expression matching correct inline iteration names
|
||||||
|
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
|
||||||
|
|
||||||
|
# Naming hint for inline iteration names
|
||||||
|
inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
|
||||||
|
|
||||||
|
# Regular expression matching correct class names
|
||||||
|
class-rgx=[A-Z_][a-zA-Z0-9]+$
|
||||||
|
|
||||||
|
# Naming hint for class names
|
||||||
|
class-name-hint=[A-Z_][a-zA-Z0-9]+$
|
||||||
|
|
||||||
|
# Regular expression matching correct module names
|
||||||
|
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||||
|
|
||||||
|
# Naming hint for module names
|
||||||
|
module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||||
|
|
||||||
|
# Regular expression matching correct method names
|
||||||
|
method-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||||
|
|
||||||
|
# Naming hint for method names
|
||||||
|
method-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||||
|
|
||||||
|
# Regular expression which should only match function or class names that do
|
||||||
|
# not require a docstring.
|
||||||
|
no-docstring-rgx=^_
|
||||||
|
|
||||||
|
# Minimum line length for functions/classes that require docstrings, shorter
|
||||||
|
# ones are exempt.
|
||||||
|
docstring-min-length=-1
|
||||||
|
|
||||||
|
|
||||||
|
[ELIF]
|
||||||
|
|
||||||
|
# Maximum number of nested blocks for function / method body
|
||||||
|
max-nested-blocks=5
|
||||||
|
|
||||||
|
|
||||||
|
[DESIGN]
|
||||||
|
|
||||||
|
# Maximum number of arguments for function / method
|
||||||
|
max-args=5
|
||||||
|
|
||||||
|
# Argument names that match this expression will be ignored. Default to name
|
||||||
|
# with leading underscore
|
||||||
|
ignored-argument-names=_.*
|
||||||
|
|
||||||
|
# Maximum number of locals for function / method body
|
||||||
|
max-locals=15
|
||||||
|
|
||||||
|
# Maximum number of return / yield for function / method body
|
||||||
|
max-returns=6
|
||||||
|
|
||||||
|
# Maximum number of branch for function / method body
|
||||||
|
max-branches=12
|
||||||
|
|
||||||
|
# Maximum number of statements in function / method body
|
||||||
|
max-statements=50
|
||||||
|
|
||||||
|
# Maximum number of parents for a class (see R0901).
|
||||||
|
max-parents=7
|
||||||
|
|
||||||
|
# Maximum number of attributes for a class (see R0902).
|
||||||
|
max-attributes=7
|
||||||
|
|
||||||
|
# Minimum number of public methods for a class (see R0903).
|
||||||
|
min-public-methods=2
|
||||||
|
|
||||||
|
# Maximum number of public methods for a class (see R0904).
|
||||||
|
max-public-methods=20
|
||||||
|
|
||||||
|
# Maximum number of boolean expressions in a if statement
|
||||||
|
max-bool-expr=5
|
||||||
|
|
||||||
|
|
||||||
|
[IMPORTS]
|
||||||
|
|
||||||
|
# Deprecated modules which should not be used, separated by a comma
|
||||||
|
deprecated-modules=regsub,TERMIOS,Bastion,rexec
|
||||||
|
|
||||||
|
# Create a graph of every (i.e. internal and external) dependencies in the
|
||||||
|
# given file (report RP0402 must not be disabled)
|
||||||
|
import-graph=
|
||||||
|
|
||||||
|
# Create a graph of external dependencies in the given file (report RP0402 must
|
||||||
|
# not be disabled)
|
||||||
|
ext-import-graph=
|
||||||
|
|
||||||
|
# Create a graph of internal dependencies in the given file (report RP0402 must
|
||||||
|
# not be disabled)
|
||||||
|
int-import-graph=
|
||||||
|
|
||||||
|
|
||||||
|
[CLASSES]
|
||||||
|
|
||||||
|
# List of method names used to declare (i.e. assign) instance attributes.
|
||||||
|
defining-attr-methods=__init__,__new__,setUp
|
||||||
|
|
||||||
|
# List of valid names for the first argument in a class method.
|
||||||
|
valid-classmethod-first-arg=cls
|
||||||
|
|
||||||
|
# List of valid names for the first argument in a metaclass class method.
|
||||||
|
valid-metaclass-classmethod-first-arg=mcs
|
||||||
|
|
||||||
|
# List of member names, which should be excluded from the protected access
|
||||||
|
# warning.
|
||||||
|
exclude-protected=_asdict,_fields,_replace,_source,_make
|
||||||
|
|
||||||
|
|
||||||
|
[EXCEPTIONS]
|
||||||
|
|
||||||
|
# Exceptions that will emit a warning when being caught. Defaults to
|
||||||
|
# "Exception"
|
||||||
|
overgeneral-exceptions=Exception
|
@ -9,6 +9,7 @@ env:
|
|||||||
- DJANGO=1.7.11
|
- DJANGO=1.7.11
|
||||||
- DJANGO=1.8.7
|
- DJANGO=1.8.7
|
||||||
- DJANGO=1.9
|
- DJANGO=1.9
|
||||||
|
- DJANGO=1.10
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
exclude:
|
exclude:
|
||||||
@ -17,7 +18,13 @@ matrix:
|
|||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install argparse
|
- pip install argparse
|
||||||
|
- pip install coverage
|
||||||
|
- pip install codecov
|
||||||
- pip install -q Django==$DJANGO
|
- pip install -q Django==$DJANGO
|
||||||
- pip install -q -r requirements.txt
|
- pip install -q -r requirements.txt
|
||||||
|
|
||||||
script: python quicktest.py helpdesk
|
script:
|
||||||
|
- coverage run --source='.' quicktest.py helpdesk
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- codecov
|
@ -4,6 +4,9 @@ django-helpdesk - A Django powered ticket tracker for small businesses.
|
|||||||
.. image:: https://travis-ci.org/django-helpdesk/django-helpdesk.png?branch=master
|
.. image:: https://travis-ci.org/django-helpdesk/django-helpdesk.png?branch=master
|
||||||
:target: https://travis-ci.org/django-helpdesk/django-helpdesk
|
:target: https://travis-ci.org/django-helpdesk/django-helpdesk
|
||||||
|
|
||||||
|
.. image:: https://codecov.io/gh/django-helpdesk/django-helpdesk/branch/master/graph/badge.svg
|
||||||
|
:target: https://codecov.io/gh/django-helpdesk/django-helpdesk
|
||||||
|
|
||||||
Copyright 2009- Ross Poulton and contributors. All Rights Reserved. See LICENSE for details.
|
Copyright 2009- Ross Poulton and contributors. All Rights Reserved. See LICENSE for details.
|
||||||
|
|
||||||
django-helpdesk was formerly known as Jutda Helpdesk, named after the
|
django-helpdesk was formerly known as Jutda Helpdesk, named after the
|
||||||
@ -26,7 +29,7 @@ Dependencies (pre-flight checklist)
|
|||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
1. Python 2.7 or 3.4+ (3.4+ support is new, please let us know how it goes)
|
1. Python 2.7 or 3.4+ (3.4+ support is new, please let us know how it goes)
|
||||||
2. Django (1.7 or newer, preferably 1.9 - Django 1.7 is not supported if you are using Python 3.5)
|
2. Django (1.7, 1.8, 1.9 and 1.10, preferably 1.9 - Django 1.7 is not supported if you are using Python 3.5)
|
||||||
3. An existing WORKING Django project with database etc. If you
|
3. An existing WORKING Django project with database etc. If you
|
||||||
cannot log into the Admin, you won't get this product working.
|
cannot log into the Admin, you won't get this product working.
|
||||||
4. `pip install django-bootstrap-form` and add `bootstrapform` to `settings.INSTALLED_APPS`
|
4. `pip install django-bootstrap-form` and add `bootstrapform` to `settings.INSTALLED_APPS`
|
||||||
@ -99,6 +102,3 @@ https://www.transifex.com/django-helpdesk/django-helpdesk/
|
|||||||
Feel free to request access to contribute your translations.
|
Feel free to request access to contribute your translations.
|
||||||
|
|
||||||
Pull requests for all other changes are welcome. We're currently trying to add test cases wherever possible, so please continue to include tests with pull requests.
|
Pull requests for all other changes are welcome. We're currently trying to add test cases wherever possible, so please continue to include tests with pull requests.
|
||||||
|
|
||||||
.. image:: https://secure.travis-ci.org/django-helpdesk/django-helpdesk.png?branch=master
|
|
||||||
:target: https://travis-ci.org/django-helpdesk/django-helpdesk
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
Settings
|
Settings
|
||||||
========
|
========
|
||||||
|
|
||||||
First, django-helpdesk needs ``django.core.context_processors.request`` activated, so in your ``settings.py`` add::
|
First, django-helpdesk needs ``django.core.context_processors.request`` activated, so you must add it to the ``settings.py``. For Django 1.7, add::
|
||||||
|
|
||||||
from django.conf import global_settings
|
from django.conf import global_settings
|
||||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||||
@ -9,6 +9,25 @@ First, django-helpdesk needs ``django.core.context_processors.request`` activat
|
|||||||
('django.core.context_processors.request',)
|
('django.core.context_processors.request',)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
For Django 1.8 and onwards, the settings are located in the ``TEMPLATES``, and the ``request`` module has moved. Add the following instead::
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
...
|
||||||
|
'OPTIONS': {
|
||||||
|
...
|
||||||
|
'context_processors': (
|
||||||
|
# Default ones first
|
||||||
|
...
|
||||||
|
# The one django-helpdesk requires:
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
The following settings can be changed in your ``settings.py`` file to help change the way django-helpdesk operates. There are quite a few settings available to toggle functionality within django-helpdesk.
|
The following settings can be changed in your ``settings.py`` file to help change the way django-helpdesk operates. There are quite a few settings available to toggle functionality within django-helpdesk.
|
||||||
|
|
||||||
HELPDESK_DEFAULT_SETTINGS
|
HELPDESK_DEFAULT_SETTINGS
|
||||||
|
@ -5,10 +5,14 @@ from helpdesk.models import EscalationExclusion, EmailTemplate, KBItem
|
|||||||
from helpdesk.models import TicketChange, Attachment, IgnoreEmail
|
from helpdesk.models import TicketChange, Attachment, IgnoreEmail
|
||||||
from helpdesk.models import CustomField
|
from helpdesk.models import CustomField
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Queue)
|
||||||
class QueueAdmin(admin.ModelAdmin):
|
class QueueAdmin(admin.ModelAdmin):
|
||||||
list_display = ('title', 'slug', 'email_address', 'locale')
|
list_display = ('title', 'slug', 'email_address', 'locale')
|
||||||
prepopulated_fields = {"slug": ("title",)}
|
prepopulated_fields = {"slug": ("title",)}
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Ticket)
|
||||||
class TicketAdmin(admin.ModelAdmin):
|
class TicketAdmin(admin.ModelAdmin):
|
||||||
list_display = ('title', 'status', 'assigned_to', 'queue', 'hidden_submitter_email',)
|
list_display = ('title', 'status', 'assigned_to', 'queue', 'hidden_submitter_email',)
|
||||||
date_hierarchy = 'created'
|
date_hierarchy = 'created'
|
||||||
@ -24,34 +28,38 @@ class TicketAdmin(admin.ModelAdmin):
|
|||||||
return ticket.submitter_email
|
return ticket.submitter_email
|
||||||
hidden_submitter_email.short_description = _('Submitter E-Mail')
|
hidden_submitter_email.short_description = _('Submitter E-Mail')
|
||||||
|
|
||||||
|
|
||||||
class TicketChangeInline(admin.StackedInline):
|
class TicketChangeInline(admin.StackedInline):
|
||||||
model = TicketChange
|
model = TicketChange
|
||||||
|
|
||||||
|
|
||||||
class AttachmentInline(admin.StackedInline):
|
class AttachmentInline(admin.StackedInline):
|
||||||
model = Attachment
|
model = Attachment
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(FollowUp)
|
||||||
class FollowUpAdmin(admin.ModelAdmin):
|
class FollowUpAdmin(admin.ModelAdmin):
|
||||||
inlines = [TicketChangeInline, AttachmentInline]
|
inlines = [TicketChangeInline, AttachmentInline]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(KBItem)
|
||||||
class KBItemAdmin(admin.ModelAdmin):
|
class KBItemAdmin(admin.ModelAdmin):
|
||||||
list_display = ('category', 'title', 'last_updated',)
|
list_display = ('category', 'title', 'last_updated',)
|
||||||
list_display_links = ('title',)
|
list_display_links = ('title',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(CustomField)
|
||||||
class CustomFieldAdmin(admin.ModelAdmin):
|
class CustomFieldAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'label', 'data_type')
|
list_display = ('name', 'label', 'data_type')
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(EmailTemplate)
|
||||||
class EmailTemplateAdmin(admin.ModelAdmin):
|
class EmailTemplateAdmin(admin.ModelAdmin):
|
||||||
list_display = ('template_name', 'heading', 'locale')
|
list_display = ('template_name', 'heading', 'locale')
|
||||||
list_filter = ('locale', )
|
list_filter = ('locale', )
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Ticket, TicketAdmin)
|
|
||||||
admin.site.register(Queue, QueueAdmin)
|
|
||||||
admin.site.register(FollowUp, FollowUpAdmin)
|
|
||||||
admin.site.register(PreSetReply)
|
admin.site.register(PreSetReply)
|
||||||
admin.site.register(EscalationExclusion)
|
admin.site.register(EscalationExclusion)
|
||||||
admin.site.register(EmailTemplate, EmailTemplateAdmin)
|
|
||||||
admin.site.register(KBCategory)
|
admin.site.register(KBCategory)
|
||||||
admin.site.register(KBItem, KBItemAdmin)
|
|
||||||
admin.site.register(IgnoreEmail)
|
admin.site.register(IgnoreEmail)
|
||||||
admin.site.register(CustomField, CustomFieldAdmin)
|
|
||||||
|
@ -55,7 +55,7 @@ Usage example::
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import os, sys
|
import os
|
||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
@ -104,9 +104,13 @@ else:
|
|||||||
|
|
||||||
class AkismetError(Exception):
|
class AkismetError(Exception):
|
||||||
"""Base class for all akismet exceptions."""
|
"""Base class for all akismet exceptions."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class APIKeyError(AkismetError):
|
class APIKeyError(AkismetError):
|
||||||
"""Invalid API key."""
|
"""Invalid API key."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Akismet(object):
|
class Akismet(object):
|
||||||
"""A class for working with the akismet API"""
|
"""A class for working with the akismet API"""
|
||||||
@ -120,7 +124,6 @@ class Akismet(object):
|
|||||||
self.user_agent = user_agent % (agent, __version__)
|
self.user_agent = user_agent % (agent, __version__)
|
||||||
self.setAPIKey(key, blog_url)
|
self.setAPIKey(key, blog_url)
|
||||||
|
|
||||||
|
|
||||||
def _getURL(self):
|
def _getURL(self):
|
||||||
"""
|
"""
|
||||||
Fetch the url to make requests to.
|
Fetch the url to make requests to.
|
||||||
@ -129,7 +132,6 @@ class Akismet(object):
|
|||||||
"""
|
"""
|
||||||
return 'http://%s.%s' % (self.key, self.baseurl)
|
return 'http://%s.%s' % (self.key, self.baseurl)
|
||||||
|
|
||||||
|
|
||||||
def _safeRequest(self, url, data, headers):
|
def _safeRequest(self, url, data, headers):
|
||||||
try:
|
try:
|
||||||
resp = _fetch_url(url, data, headers)
|
resp = _fetch_url(url, data, headers)
|
||||||
@ -137,7 +139,6 @@ class Akismet(object):
|
|||||||
raise AkismetError(str(e))
|
raise AkismetError(str(e))
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
@ -161,7 +162,6 @@ class Akismet(object):
|
|||||||
self.key = key
|
self.key = key
|
||||||
self.blog_url = blog_url
|
self.blog_url = blog_url
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
@ -226,14 +226,11 @@ class Akismet(object):
|
|||||||
data.setdefault('SERVER_ADMIN', os.environ.get('SERVER_ADMIN', ''))
|
data.setdefault('SERVER_ADMIN', os.environ.get('SERVER_ADMIN', ''))
|
||||||
data.setdefault('SERVER_NAME', os.environ.get('SERVER_NAME', ''))
|
data.setdefault('SERVER_NAME', os.environ.get('SERVER_NAME', ''))
|
||||||
data.setdefault('SERVER_PORT', os.environ.get('SERVER_PORT', ''))
|
data.setdefault('SERVER_PORT', os.environ.get('SERVER_PORT', ''))
|
||||||
data.setdefault('SERVER_SIGNATURE', os.environ.get('SERVER_SIGNATURE',
|
data.setdefault('SERVER_SIGNATURE', os.environ.get('SERVER_SIGNATURE', ''))
|
||||||
''))
|
data.setdefault('SERVER_SOFTWARE', os.environ.get('SERVER_SOFTWARE', ''))
|
||||||
data.setdefault('SERVER_SOFTWARE', os.environ.get('SERVER_SOFTWARE',
|
|
||||||
''))
|
|
||||||
data.setdefault('HTTP_ACCEPT', os.environ.get('HTTP_ACCEPT', ''))
|
data.setdefault('HTTP_ACCEPT', os.environ.get('HTTP_ACCEPT', ''))
|
||||||
data.setdefault('blog', self.blog_url)
|
data.setdefault('blog', self.blog_url)
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
@ -329,7 +326,6 @@ class Akismet(object):
|
|||||||
# NOTE: Happens when you get a 'howdy wilbur' response !
|
# NOTE: Happens when you get a 'howdy wilbur' response !
|
||||||
raise AkismetError('missing required argument.')
|
raise AkismetError('missing required argument.')
|
||||||
|
|
||||||
|
|
||||||
def submit_spam(self, comment, data=None, build_data=True):
|
def submit_spam(self, comment, data=None, build_data=True):
|
||||||
"""
|
"""
|
||||||
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,
|
||||||
@ -350,7 +346,6 @@ class Akismet(object):
|
|||||||
headers = {'User-Agent': self.user_agent}
|
headers = {'User-Agent': self.user_agent}
|
||||||
self._safeRequest(url, urlencode(data), headers)
|
self._safeRequest(url, urlencode(data), headers)
|
||||||
|
|
||||||
|
|
||||||
def submit_ham(self, comment, data=None, build_data=True):
|
def submit_ham(self, comment, data=None, build_data=True):
|
||||||
"""
|
"""
|
||||||
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,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class HelpdeskConfig(AppConfig):
|
class HelpdeskConfig(AppConfig):
|
||||||
name = 'helpdesk'
|
name = 'helpdesk'
|
||||||
verbose_name = "Helpdesk"
|
verbose_name = "Helpdesk"
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ django-helpdesk - A Django powered ticket tracker for small enterprise.
|
|||||||
forms.py - Definitions of newforms-based forms for creating and maintaining
|
forms.py - Definitions of newforms-based forms for creating and maintaining
|
||||||
tickets.
|
tickets.
|
||||||
"""
|
"""
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -13,27 +15,27 @@ except ImportError:
|
|||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.forms import extras
|
from django.forms import extras
|
||||||
from django.core.files.storage import default_storage
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
try:
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
User = get_user_model()
|
|
||||||
except ImportError:
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
try:
|
try:
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from datetime import datetime as timezone
|
from datetime import datetime as timezone
|
||||||
|
|
||||||
from helpdesk.lib import send_templated_mail, safe_template_context
|
from helpdesk.lib import send_templated_mail, safe_template_context
|
||||||
from helpdesk.models import Ticket, Queue, FollowUp, Attachment, IgnoreEmail, TicketCC, CustomField, TicketCustomFieldValue, TicketDependency
|
from helpdesk.models import (Ticket, Queue, FollowUp, Attachment, IgnoreEmail, TicketCC,
|
||||||
|
CustomField, TicketCustomFieldValue, TicketDependency)
|
||||||
from helpdesk import settings as helpdesk_settings
|
from helpdesk import settings as helpdesk_settings
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldMixin(object):
|
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
|
||||||
@ -73,7 +75,9 @@ class CustomFieldMixin(object):
|
|||||||
|
|
||||||
self.fields['custom_%s' % field.name] = fieldclass(**instanceargs)
|
self.fields['custom_%s' % field.name] = fieldclass(**instanceargs)
|
||||||
|
|
||||||
|
|
||||||
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')
|
||||||
@ -99,7 +103,6 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm):
|
|||||||
|
|
||||||
self.customfield_to_field(field, instanceargs)
|
self.customfield_to_field(field, instanceargs)
|
||||||
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
for field, value in self.cleaned_data.items():
|
for field, value in self.cleaned_data.items():
|
||||||
@ -108,7 +111,7 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm):
|
|||||||
customfield = CustomField.objects.get(name=field_name)
|
customfield = CustomField.objects.get(name=field_name)
|
||||||
try:
|
try:
|
||||||
cfv = TicketCustomFieldValue.objects.get(ticket=self.instance, field=customfield)
|
cfv = TicketCustomFieldValue.objects.get(ticket=self.instance, field=customfield)
|
||||||
except:
|
except ObjectDoesNotExist:
|
||||||
cfv = TicketCustomFieldValue(ticket=self.instance, field=customfield)
|
cfv = TicketCustomFieldValue(ticket=self.instance, field=customfield)
|
||||||
cfv.value = value
|
cfv.value = value
|
||||||
cfv.save()
|
cfv.save()
|
||||||
@ -117,14 +120,17 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class EditFollowUpForm(forms.ModelForm):
|
class EditFollowUpForm(forms.ModelForm):
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
"Filter not openned tickets here."
|
|
||||||
super(EditFollowUpForm, self).__init__(*args, **kwargs)
|
|
||||||
self.fields["ticket"].queryset = Ticket.objects.filter(status__in=(Ticket.OPEN_STATUS, Ticket.REOPENED_STATUS))
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FollowUp
|
model = FollowUp
|
||||||
exclude = ('date', 'user',)
|
exclude = ('date', 'user',)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""Filter not openned tickets here."""
|
||||||
|
super(EditFollowUpForm, self).__init__(*args, **kwargs)
|
||||||
|
self.fields["ticket"].queryset = Ticket.objects.filter(status__in=(Ticket.OPEN_STATUS, Ticket.REOPENED_STATUS))
|
||||||
|
|
||||||
|
|
||||||
class TicketForm(CustomFieldMixin, forms.Form):
|
class TicketForm(CustomFieldMixin, forms.Form):
|
||||||
queue = forms.ChoiceField(
|
queue = forms.ChoiceField(
|
||||||
label=_('Queue'),
|
label=_('Queue'),
|
||||||
@ -166,8 +172,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
required=False,
|
required=False,
|
||||||
initial='3',
|
initial='3',
|
||||||
label=_('Priority'),
|
label=_('Priority'),
|
||||||
help_text=_('Please select a priority carefully. If unsure, leave it '
|
help_text=_('Please select a priority carefully. If unsure, leave it as \'3\'.'),
|
||||||
'as \'3\'.'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
due_date = forms.DateTimeField(
|
due_date = forms.DateTimeField(
|
||||||
@ -203,7 +208,6 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
|
|
||||||
self.customfield_to_field(field, instanceargs)
|
self.customfield_to_field(field, instanceargs)
|
||||||
|
|
||||||
|
|
||||||
def save(self, user):
|
def save(self, user):
|
||||||
"""
|
"""
|
||||||
Writes and returns a Ticket() object
|
Writes and returns a Ticket() object
|
||||||
@ -290,7 +294,11 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
)
|
)
|
||||||
messages_sent_to.append(t.submitter_email)
|
messages_sent_to.append(t.submitter_email)
|
||||||
|
|
||||||
if t.assigned_to and t.assigned_to != user and t.assigned_to.usersettings.settings.get('email_on_ticket_assign', False) and t.assigned_to.email and t.assigned_to.email not in messages_sent_to:
|
if t.assigned_to and \
|
||||||
|
t.assigned_to != user and \
|
||||||
|
t.assigned_to.usersettings.settings.get('email_on_ticket_assign', False) and \
|
||||||
|
t.assigned_to.email and \
|
||||||
|
t.assigned_to.email not in messages_sent_to:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
'assigned_owner',
|
'assigned_owner',
|
||||||
context,
|
context,
|
||||||
@ -312,7 +320,9 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
)
|
)
|
||||||
messages_sent_to.append(q.new_ticket_cc)
|
messages_sent_to.append(q.new_ticket_cc)
|
||||||
|
|
||||||
if q.updated_ticket_cc and q.updated_ticket_cc != q.new_ticket_cc and q.updated_ticket_cc not in messages_sent_to:
|
if q.updated_ticket_cc and \
|
||||||
|
q.updated_ticket_cc != q.new_ticket_cc and \
|
||||||
|
q.updated_ticket_cc not in messages_sent_to:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
'newticket_cc',
|
'newticket_cc',
|
||||||
context,
|
context,
|
||||||
@ -463,7 +473,10 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
)
|
)
|
||||||
messages_sent_to.append(t.submitter_email)
|
messages_sent_to.append(t.submitter_email)
|
||||||
|
|
||||||
if t.assigned_to and t.assigned_to.usersettings.settings.get('email_on_ticket_assign', False) and t.assigned_to.email and t.assigned_to.email not in messages_sent_to:
|
if t.assigned_to and \
|
||||||
|
t.assigned_to.usersettings.settings.get('email_on_ticket_assign', False) and \
|
||||||
|
t.assigned_to.email and \
|
||||||
|
t.assigned_to.email not in messages_sent_to:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
'assigned_owner',
|
'assigned_owner',
|
||||||
context,
|
context,
|
||||||
@ -485,7 +498,9 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
)
|
)
|
||||||
messages_sent_to.append(q.new_ticket_cc)
|
messages_sent_to.append(q.new_ticket_cc)
|
||||||
|
|
||||||
if q.updated_ticket_cc and q.updated_ticket_cc != q.new_ticket_cc and q.updated_ticket_cc not in messages_sent_to:
|
if q.updated_ticket_cc and \
|
||||||
|
q.updated_ticket_cc != q.new_ticket_cc and \
|
||||||
|
q.updated_ticket_cc not in messages_sent_to:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
'newticket_cc',
|
'newticket_cc',
|
||||||
context,
|
context,
|
||||||
@ -537,12 +552,20 @@ class UserSettingsForm(forms.Form):
|
|||||||
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:
|
||||||
|
model = TicketCC
|
||||||
|
exclude = ('ticket',)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(TicketCCForm, self).__init__(*args, **kwargs)
|
super(TicketCCForm, self).__init__(*args, **kwargs)
|
||||||
if helpdesk_settings.HELPDESK_STAFF_ONLY_TICKET_CC:
|
if helpdesk_settings.HELPDESK_STAFF_ONLY_TICKET_CC:
|
||||||
@ -550,11 +573,10 @@ class TicketCCForm(forms.ModelForm):
|
|||||||
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)
|
||||||
self.fields['user'].queryset = users
|
self.fields['user'].queryset = users
|
||||||
class Meta:
|
|
||||||
model = TicketCC
|
|
||||||
exclude = ('ticket',)
|
|
||||||
|
|
||||||
class TicketDependencyForm(forms.ModelForm):
|
class TicketDependencyForm(forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TicketDependency
|
model = TicketDependency
|
||||||
exclude = ('ticket',)
|
exclude = ('ticket',)
|
||||||
|
@ -6,7 +6,7 @@ django-helpdesk - A Django powered ticket tracker for small enterprise.
|
|||||||
lib.py - Common functions (eg multipart e-mail)
|
lib.py - Common functions (eg multipart e-mail)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
chart_colours = ('80C65A', '990066', 'FF9900', '3399CC', 'BBCCED', '3399CC', 'FFCC33')
|
import logging
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from base64 import urlsafe_b64encode as b64encode
|
from base64 import urlsafe_b64encode as b64encode
|
||||||
@ -17,13 +17,20 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
from base64 import decodestring as b64decode
|
from base64 import decodestring as b64decode
|
||||||
|
|
||||||
import logging
|
|
||||||
logger = logging.getLogger('helpdesk')
|
|
||||||
|
|
||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
def send_templated_mail(template_name, email_context, recipients, sender=None, bcc=None, fail_silently=False, files=None):
|
logger = logging.getLogger('helpdesk')
|
||||||
|
|
||||||
|
|
||||||
|
def send_templated_mail(template_name,
|
||||||
|
email_context,
|
||||||
|
recipients,
|
||||||
|
sender=None,
|
||||||
|
bcc=None,
|
||||||
|
fail_silently=False,
|
||||||
|
files=None):
|
||||||
"""
|
"""
|
||||||
send_templated_mail() is a warpper around Django's e-mail routines that
|
send_templated_mail() is a warpper around Django's e-mail routines that
|
||||||
allows us to easily send multipart (text/plain & text/html) e-mails using
|
allows us to easily send multipart (text/plain & text/html) e-mails using
|
||||||
@ -83,7 +90,7 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b
|
|||||||
try:
|
try:
|
||||||
t = EmailTemplate.objects.get(template_name__iexact=template_name, locale__isnull=True)
|
t = EmailTemplate.objects.get(template_name__iexact=template_name, locale__isnull=True)
|
||||||
except EmailTemplate.DoesNotExist:
|
except EmailTemplate.DoesNotExist:
|
||||||
logger.warning('template "%s" does not exist, no mail sent' %
|
logger.warning('template "%s" does not exist, no mail sent',
|
||||||
template_name)
|
template_name)
|
||||||
return # just ignore if template doesn't exist
|
return # just ignore if template doesn't exist
|
||||||
|
|
||||||
@ -92,7 +99,8 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b
|
|||||||
|
|
||||||
footer_file = os.path.join('helpdesk', locale, 'email_text_footer.txt')
|
footer_file = os.path.join('helpdesk', locale, 'email_text_footer.txt')
|
||||||
|
|
||||||
# get_template_from_string was removed in Django 1.8 http://django.readthedocs.org/en/1.8.x/ref/templates/upgrading.html
|
# get_template_from_string was removed in Django 1.8
|
||||||
|
# http://django.readthedocs.org/en/1.8.x/ref/templates/upgrading.html
|
||||||
try:
|
try:
|
||||||
from django.template import engines
|
from django.template import engines
|
||||||
template_func = engines['django'].from_string
|
template_func = engines['django'].from_string
|
||||||
@ -105,21 +113,22 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b
|
|||||||
|
|
||||||
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')
|
||||||
|
|
||||||
|
# keep new lines in html emails
|
||||||
''' keep new lines in html emails '''
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
|
|
||||||
if 'comment' in context:
|
if 'comment' in context:
|
||||||
html_txt = context['comment']
|
html_txt = context['comment']
|
||||||
html_txt = html_txt.replace('\r\n', '<br>')
|
html_txt = html_txt.replace('\r\n', '<br>')
|
||||||
context['comment'] = mark_safe(html_txt)
|
context['comment'] = mark_safe(html_txt)
|
||||||
|
|
||||||
# get_template_from_string was removed in Django 1.8 http://django.readthedocs.org/en/1.8.x/ref/templates/upgrading.html
|
# get_template_from_string was removed in Django 1.8
|
||||||
|
# http://django.readthedocs.org/en/1.8.x/ref/templates/upgrading.html
|
||||||
html_part = template_func(
|
html_part = template_func(
|
||||||
"{%% extends '%s' %%}{%% block title %%}%s{%% endblock %%}{%% block content %%}%s{%% endblock %%}" % (email_html_base_file, t.heading, t.html)
|
"{%% extends '%s' %%}{%% block title %%}"
|
||||||
).render(context)
|
"%s"
|
||||||
|
"{%% endblock %%}{%% block content %%}%s{%% endblock %%}" %
|
||||||
|
(email_html_base_file, t.heading, t.html)).render(context)
|
||||||
|
|
||||||
# get_template_from_string was removed in Django 1.8 http://django.readthedocs.org/en/1.8.x/ref/templates/upgrading.html
|
# get_template_from_string was removed in Django 1.8
|
||||||
|
# http://django.readthedocs.org/en/1.8.x/ref/templates/upgrading.html
|
||||||
subject_part = template_func(
|
subject_part = template_func(
|
||||||
HELPDESK_EMAIL_SUBJECT_TEMPLATE % {
|
HELPDESK_EMAIL_SUBJECT_TEMPLATE % {
|
||||||
"subject": t.subject,
|
"subject": t.subject,
|
||||||
@ -131,11 +140,9 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b
|
|||||||
elif type(recipients) != list:
|
elif type(recipients) != list:
|
||||||
recipients = [recipients, ]
|
recipients = [recipients, ]
|
||||||
|
|
||||||
msg = EmailMultiAlternatives( subject_part.replace('\n', '').replace('\r', ''),
|
msg = EmailMultiAlternatives(
|
||||||
text_part,
|
subject_part.replace('\n', '').replace('\r', ''),
|
||||||
sender,
|
text_part, sender, recipients, bcc=bcc)
|
||||||
recipients,
|
|
||||||
bcc=bcc)
|
|
||||||
msg.attach_alternative(html_part, "text/html")
|
msg.attach_alternative(html_part, "text/html")
|
||||||
|
|
||||||
if files:
|
if files:
|
||||||
@ -226,7 +233,7 @@ def safe_template_context(ticket):
|
|||||||
|
|
||||||
context = {
|
context = {
|
||||||
'queue': {},
|
'queue': {},
|
||||||
'ticket': {},
|
'ticket': {}
|
||||||
}
|
}
|
||||||
queue = ticket.queue
|
queue = ticket.queue
|
||||||
|
|
||||||
|
@ -16,12 +16,12 @@ from optparse import make_option
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
from helpdesk.models import EscalationExclusion, Queue
|
from helpdesk.models import EscalationExclusion, Queue
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
BaseCommand.__init__(self)
|
BaseCommand.__init__(self)
|
||||||
|
|
||||||
@ -47,7 +47,8 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
days = options['days']
|
days = options['days']
|
||||||
occurrences = options['occurrences']
|
# optparse should already handle the `or 1`
|
||||||
|
occurrences = options['occurrences'] or 1
|
||||||
verbose = False
|
verbose = False
|
||||||
queue_slugs = options['queues']
|
queue_slugs = options['queues']
|
||||||
queues = []
|
queues = []
|
||||||
@ -55,8 +56,6 @@ class Command(BaseCommand):
|
|||||||
if options['escalate-verbosely']:
|
if options['escalate-verbosely']:
|
||||||
verbose = True
|
verbose = True
|
||||||
|
|
||||||
# this should already be handled by optparse
|
|
||||||
if not occurrences: occurrences = 1
|
|
||||||
if not (days and occurrences):
|
if not (days and occurrences):
|
||||||
raise CommandError('One or more occurrences must be specified.')
|
raise CommandError('One or more occurrences must be specified.')
|
||||||
|
|
||||||
@ -116,7 +115,6 @@ def usage():
|
|||||||
print(" --verbose, -v: Display a list of dates excluded")
|
print(" --verbose, -v: Display a list of dates excluded")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# This script can be run from the command-line or via Django's manage.py.
|
# This script can be run from the command-line or via Django's manage.py.
|
||||||
try:
|
try:
|
||||||
@ -126,7 +124,7 @@ if __name__ == '__main__':
|
|||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
days = None
|
days = None
|
||||||
occurrences = None
|
occurrences = 1
|
||||||
verbose = False
|
verbose = False
|
||||||
queue_slugs = None
|
queue_slugs = None
|
||||||
queues = []
|
queues = []
|
||||||
@ -139,9 +137,8 @@ if __name__ == '__main__':
|
|||||||
if o in ('-q', '--queues'):
|
if o in ('-q', '--queues'):
|
||||||
queue_slugs = a
|
queue_slugs = a
|
||||||
if o in ('-o', '--occurrences'):
|
if o in ('-o', '--occurrences'):
|
||||||
occurrences = int(a)
|
occurrences = int(a) or 1
|
||||||
|
|
||||||
if not occurrences: occurrences = 1
|
|
||||||
if not (days and occurrences):
|
if not (days and occurrences):
|
||||||
usage()
|
usage()
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
@ -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")
|
||||||
|
|
||||||
|
@ -10,17 +10,16 @@ users who don't yet have them.
|
|||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
try:
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
User = get_user_model()
|
|
||||||
except ImportError:
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
|
|
||||||
from helpdesk.models import UserSettings
|
from helpdesk.models import UserSettings
|
||||||
from helpdesk.settings import DEFAULT_USER_SETTINGS
|
from helpdesk.settings import DEFAULT_USER_SETTINGS
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
"create_usersettings command"
|
"""create_usersettings command"""
|
||||||
|
|
||||||
help = _('Check for user without django-helpdesk UserSettings '
|
help = _('Check for user without django-helpdesk UserSettings '
|
||||||
'and create settings if required. Uses '
|
'and create settings if required. Uses '
|
||||||
@ -28,10 +27,7 @@ class Command(BaseCommand):
|
|||||||
'suit your situation.')
|
'suit your situation.')
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
"handle command line"
|
"""handle command line"""
|
||||||
for u in User.objects.all():
|
for u in User.objects.all():
|
||||||
try:
|
UserSettings.objects.get_or_create(user=u,
|
||||||
s = UserSettings.objects.get(user=u)
|
defaults={'settings': DEFAULT_USER_SETTINGS})
|
||||||
except UserSettings.DoesNotExist:
|
|
||||||
s = UserSettings(user=u, settings=DEFAULT_USER_SETTINGS)
|
|
||||||
s.save()
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ class Command(BaseCommand):
|
|||||||
queue_set = queue_slugs.split(',')
|
queue_set = queue_slugs.split(',')
|
||||||
for queue in queue_set:
|
for queue in queue_set:
|
||||||
try:
|
try:
|
||||||
q = Queue.objects.get(slug__exact=queue)
|
Queue.objects.get(slug__exact=queue)
|
||||||
except Queue.DoesNotExist:
|
except Queue.DoesNotExist:
|
||||||
raise CommandError("Queue %s does not exist." % queue)
|
raise CommandError("Queue %s does not exist." % queue)
|
||||||
queues.append(queue)
|
queues.append(queue)
|
||||||
@ -82,23 +83,22 @@ def escalate_tickets(queues, verbose):
|
|||||||
days += 1
|
days += 1
|
||||||
workdate = workdate + timedelta(days=1)
|
workdate = workdate + timedelta(days=1)
|
||||||
|
|
||||||
|
|
||||||
req_last_escl_date = date.today() - timedelta(days=days)
|
req_last_escl_date = date.today() - timedelta(days=days)
|
||||||
|
|
||||||
if verbose:
|
if 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()
|
||||||
|
@ -40,7 +40,17 @@ from helpdesk.lib import send_templated_mail, safe_template_context
|
|||||||
from helpdesk.models import Queue, Ticket, FollowUp, Attachment, IgnoreEmail
|
from helpdesk.models import Queue, Ticket, FollowUp, Attachment, IgnoreEmail
|
||||||
|
|
||||||
|
|
||||||
|
STRIPPED_SUBJECT_STRINGS = [
|
||||||
|
"Re: ",
|
||||||
|
"Fw: ",
|
||||||
|
"RE: ",
|
||||||
|
"FW: ",
|
||||||
|
"Automatic reply: ",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
BaseCommand.__init__(self)
|
BaseCommand.__init__(self)
|
||||||
|
|
||||||
@ -52,7 +62,8 @@ class Command(BaseCommand):
|
|||||||
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 POP3/IMAP as required, feeding them into the helpdesk.'
|
help = 'Process Jutda Helpdesk queues and process e-mails via ' \
|
||||||
|
'POP3/IMAP as required, feeding them into the helpdesk.'
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
quiet = options.get('quiet', False)
|
quiet = options.get('quiet', False)
|
||||||
@ -70,7 +81,6 @@ def process_email(quiet=False):
|
|||||||
if not q.email_box_interval:
|
if not q.email_box_interval:
|
||||||
q.email_box_interval = 0
|
q.email_box_interval = 0
|
||||||
|
|
||||||
|
|
||||||
queue_time_delta = timedelta(minutes=q.email_box_interval)
|
queue_time_delta = timedelta(minutes=q.email_box_interval)
|
||||||
|
|
||||||
if (q.email_box_last_check + queue_time_delta) > timezone.now():
|
if (q.email_box_last_check + queue_time_delta) > timezone.now():
|
||||||
@ -90,39 +100,47 @@ def process_queue(q, quiet=False):
|
|||||||
try:
|
try:
|
||||||
import socks
|
import socks
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise ImportError("Queue has been configured with proxy settings, but no socks library was installed. Try to install PySocks via pypi.")
|
raise ImportError("Queue has been configured with proxy settings, "
|
||||||
|
"but no socks library was installed. "
|
||||||
|
"Try to install PySocks via pypi.")
|
||||||
|
|
||||||
proxy_type = {
|
proxy_type = {
|
||||||
'socks4': socks.SOCKS4,
|
'socks4': socks.SOCKS4,
|
||||||
'socks5': socks.SOCKS5,
|
'socks5': socks.SOCKS5,
|
||||||
}.get(q.socks_proxy_type)
|
}.get(q.socks_proxy_type)
|
||||||
|
|
||||||
socks.set_default_proxy(proxy_type=proxy_type, addr=q.socks_proxy_host, port=q.socks_proxy_port)
|
socks.set_default_proxy(proxy_type=proxy_type,
|
||||||
|
addr=q.socks_proxy_host,
|
||||||
|
port=q.socks_proxy_port)
|
||||||
socket.socket = socks.socksocket
|
socket.socket = socks.socksocket
|
||||||
else:
|
else:
|
||||||
socket.socket = socket._socketobject
|
socket.socket = socket._socketobject
|
||||||
|
|
||||||
email_box_type = settings.QUEUE_EMAIL_BOX_TYPE if settings.QUEUE_EMAIL_BOX_TYPE else q.email_box_type
|
email_box_type = settings.QUEUE_EMAIL_BOX_TYPE or q.email_box_type
|
||||||
|
|
||||||
if email_box_type == 'pop3':
|
if email_box_type == 'pop3':
|
||||||
|
|
||||||
if q.email_box_ssl or settings.QUEUE_EMAIL_BOX_SSL:
|
if q.email_box_ssl or settings.QUEUE_EMAIL_BOX_SSL:
|
||||||
if not q.email_box_port: q.email_box_port = 995
|
if not q.email_box_port:
|
||||||
server = poplib.POP3_SSL(q.email_box_host or settings.QUEUE_EMAIL_BOX_HOST, int(q.email_box_port))
|
q.email_box_port = 995
|
||||||
|
server = poplib.POP3_SSL(q.email_box_host or
|
||||||
|
settings.QUEUE_EMAIL_BOX_HOST,
|
||||||
|
int(q.email_box_port))
|
||||||
else:
|
else:
|
||||||
if not q.email_box_port: q.email_box_port = 110
|
if not q.email_box_port:
|
||||||
server = poplib.POP3(q.email_box_host or settings.QUEUE_EMAIL_BOX_HOST, int(q.email_box_port))
|
q.email_box_port = 110
|
||||||
|
server = poplib.POP3(q.email_box_host or
|
||||||
|
settings.QUEUE_EMAIL_BOX_HOST,
|
||||||
|
int(q.email_box_port))
|
||||||
|
|
||||||
server.getwelcome()
|
server.getwelcome()
|
||||||
server.user(q.email_box_user or settings.QUEUE_EMAIL_BOX_USER)
|
server.user(q.email_box_user or settings.QUEUE_EMAIL_BOX_USER)
|
||||||
server.pass_(q.email_box_pass or settings.QUEUE_EMAIL_BOX_PASSWORD)
|
server.pass_(q.email_box_pass or settings.QUEUE_EMAIL_BOX_PASSWORD)
|
||||||
|
|
||||||
|
|
||||||
messagesInfo = server.list()[1]
|
messagesInfo = server.list()[1]
|
||||||
|
|
||||||
for msg in messagesInfo:
|
for msg in messagesInfo:
|
||||||
msgNum = msg.split(" ")[0]
|
msgNum = msg.split(" ")[0]
|
||||||
msgSize = msg.split(" ")[1]
|
# msgSize = msg.split(" ")[1]
|
||||||
|
|
||||||
full_message = "\n".join(server.retr(msgNum)[1])
|
full_message = "\n".join(server.retr(msgNum)[1])
|
||||||
ticket = ticket_from_message(message=full_message, queue=q, quiet=quiet)
|
ticket = ticket_from_message(message=full_message, queue=q, quiet=quiet)
|
||||||
@ -134,13 +152,22 @@ def process_queue(q, quiet=False):
|
|||||||
|
|
||||||
elif email_box_type == 'imap':
|
elif email_box_type == 'imap':
|
||||||
if q.email_box_ssl or settings.QUEUE_EMAIL_BOX_SSL:
|
if q.email_box_ssl or settings.QUEUE_EMAIL_BOX_SSL:
|
||||||
if not q.email_box_port: q.email_box_port = 993
|
if not q.email_box_port:
|
||||||
server = imaplib.IMAP4_SSL(q.email_box_host or settings.QUEUE_EMAIL_BOX_HOST, int(q.email_box_port))
|
q.email_box_port = 993
|
||||||
|
server = imaplib.IMAP4_SSL(q.email_box_host or
|
||||||
|
settings.QUEUE_EMAIL_BOX_HOST,
|
||||||
|
int(q.email_box_port))
|
||||||
else:
|
else:
|
||||||
if not q.email_box_port: q.email_box_port = 143
|
if not q.email_box_port:
|
||||||
server = imaplib.IMAP4(q.email_box_host or settings.QUEUE_EMAIL_BOX_HOST, int(q.email_box_port))
|
q.email_box_port = 143
|
||||||
|
server = imaplib.IMAP4(q.email_box_host or
|
||||||
|
settings.QUEUE_EMAIL_BOX_HOST,
|
||||||
|
int(q.email_box_port))
|
||||||
|
|
||||||
server.login(q.email_box_user or settings.QUEUE_EMAIL_BOX_USER, q.email_box_pass or settings.QUEUE_EMAIL_BOX_PASSWORD)
|
server.login(q.email_box_user or
|
||||||
|
settings.QUEUE_EMAIL_BOX_USER,
|
||||||
|
q.email_box_pass or
|
||||||
|
settings.QUEUE_EMAIL_BOX_PASSWORD)
|
||||||
server.select(q.email_box_imap_folder)
|
server.select(q.email_box_imap_folder)
|
||||||
|
|
||||||
status, data = server.search(None, 'NOT', 'DELETED')
|
status, data = server.search(None, 'NOT', 'DELETED')
|
||||||
@ -165,17 +192,21 @@ def decodeUnknown(charset, string):
|
|||||||
return string.decode('iso8859-1', 'ignore')
|
return string.decode('iso8859-1', 'ignore')
|
||||||
return unicode(string, charset)
|
return unicode(string, charset)
|
||||||
|
|
||||||
|
|
||||||
def decode_mail_headers(string):
|
def decode_mail_headers(string):
|
||||||
decoded = decode_header(string)
|
decoded = decode_header(string)
|
||||||
return u' '.join([unicode(msg, charset or 'utf-8') for msg, charset in decoded])
|
return u' '.join([unicode(msg, charset or 'utf-8') for msg, charset in decoded])
|
||||||
|
|
||||||
|
|
||||||
def ticket_from_message(message, queue, quiet):
|
def ticket_from_message(message, queue, quiet):
|
||||||
# 'message' must be an RFC822 formatted message.
|
# 'message' must be an RFC822 formatted message.
|
||||||
msg = message
|
msg = message
|
||||||
message = email.message_from_string(msg)
|
message = email.message_from_string(msg)
|
||||||
subject = message.get('subject', _('Created from e-mail'))
|
subject = message.get('subject', _('Created from e-mail'))
|
||||||
subject = decode_mail_headers(decodeUnknown(message.get_charset(), subject))
|
subject = decode_mail_headers(decodeUnknown(message.get_charset(), subject))
|
||||||
subject = subject.replace("Re: ", "").replace("Fw: ", "").replace("RE: ", "").replace("FW: ", "").replace("Automatic reply: ", "").strip()
|
for affix in STRIPPED_SUBJECT_STRINGS:
|
||||||
|
subject = subject.replace(affix, "")
|
||||||
|
subject = subject.strip()
|
||||||
|
|
||||||
sender = message.get('from', _('Unknown Sender'))
|
sender = message.get('from', _('Unknown Sender'))
|
||||||
sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender))
|
sender = decode_mail_headers(decodeUnknown(message.get_charset(), sender))
|
||||||
@ -210,9 +241,10 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
if name:
|
if name:
|
||||||
name = collapse_rfc2231_value(name)
|
name = collapse_rfc2231_value(name)
|
||||||
|
|
||||||
if part.get_content_maintype() == 'text' and name == None:
|
if part.get_content_maintype() == 'text' and name is None:
|
||||||
if part.get_content_subtype() == 'plain':
|
if part.get_content_subtype() == 'plain':
|
||||||
body_plain = EmailReplyParser.parse_reply(decodeUnknown(part.get_content_charset(), part.get_payload(decode=True)))
|
body_plain = EmailReplyParser.parse_reply(
|
||||||
|
decodeUnknown(part.get_content_charset(), part.get_payload(decode=True)))
|
||||||
else:
|
else:
|
||||||
body_html = part.get_payload(decode=True)
|
body_html = part.get_payload(decode=True)
|
||||||
else:
|
else:
|
||||||
@ -259,7 +291,7 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
if smtp_priority in high_priority_types or smtp_importance in high_priority_types:
|
if smtp_priority in high_priority_types or smtp_importance in high_priority_types:
|
||||||
priority = 2
|
priority = 2
|
||||||
|
|
||||||
if ticket == None:
|
if ticket is None:
|
||||||
t = Ticket(
|
t = Ticket(
|
||||||
title=subject,
|
title=subject,
|
||||||
queue=queue,
|
queue=queue,
|
||||||
@ -270,7 +302,7 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
)
|
)
|
||||||
t.save()
|
t.save()
|
||||||
new = True
|
new = True
|
||||||
update = ''
|
# update = ''
|
||||||
|
|
||||||
elif t.status == Ticket.CLOSED_STATUS:
|
elif t.status == Ticket.CLOSED_STATUS:
|
||||||
t.status = Ticket.REOPENED_STATUS
|
t.status = Ticket.REOPENED_STATUS
|
||||||
@ -308,7 +340,6 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
if not quiet:
|
if not quiet:
|
||||||
print(" - %s" % filename)
|
print(" - %s" % filename)
|
||||||
|
|
||||||
|
|
||||||
context = safe_template_context(t)
|
context = safe_template_context(t)
|
||||||
|
|
||||||
if new:
|
if new:
|
||||||
@ -343,10 +374,10 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
else:
|
else:
|
||||||
context.update(comment=f.comment)
|
context.update(comment=f.comment)
|
||||||
|
|
||||||
if t.status == Ticket.REOPENED_STATUS:
|
# if t.status == Ticket.REOPENED_STATUS:
|
||||||
update = _(' (Reopened)')
|
# update = _(' (Reopened)')
|
||||||
else:
|
# else:
|
||||||
update = _(' (Updated)')
|
# update = _(' (Updated)')
|
||||||
|
|
||||||
if t.assigned_to:
|
if t.assigned_to:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
@ -371,4 +402,3 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
process_email()
|
process_email()
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ def load_fixture(apps, schema_editor):
|
|||||||
|
|
||||||
|
|
||||||
def unload_fixture(apps, schema_editor):
|
def unload_fixture(apps, schema_editor):
|
||||||
"Delete all EmailTemplate objects"
|
"""Delete all EmailTemplate objects"""
|
||||||
|
|
||||||
objects = deserialize_fixture()
|
objects = deserialize_fixture()
|
||||||
|
|
||||||
|
@ -12,14 +12,10 @@ from django.contrib.auth.models import Permission
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _, ugettext
|
from django.utils.translation import ugettext_lazy as _, ugettext
|
||||||
from django import VERSION
|
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
|
||||||
from helpdesk import settings as helpdesk_settings
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -64,15 +60,15 @@ class Queue(models.Model):
|
|||||||
max_length=10,
|
max_length=10,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_('Locale of this queue. All correspondence in this queue will be in this language.'),
|
help_text=_('Locale of this queue. All correspondence in this '
|
||||||
|
'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 '
|
help_text=_('Should this queue be listed on the public submission form?'),
|
||||||
'form?'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
allow_email_submission = models.BooleanField(
|
allow_email_submission = models.BooleanField(
|
||||||
@ -184,7 +180,6 @@ class Queue(models.Model):
|
|||||||
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'),
|
||||||
help_text=_('How often do you wish to check this mailbox? (in Minutes)'),
|
help_text=_('How often do you wish to check this mailbox? (in Minutes)'),
|
||||||
@ -396,8 +391,7 @@ class Ticket(models.Model):
|
|||||||
_('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 '
|
help_text=_('If a ticket is on hold, it will not automatically be escalated.'),
|
||||||
'escalated.'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
description = models.TextField(
|
description = models.TextField(
|
||||||
@ -453,7 +447,7 @@ class Ticket(models.Model):
|
|||||||
""" A user-friendly ticket ID, which is a combination of ticket ID
|
""" A user-friendly ticket ID, which is a combination of ticket ID
|
||||||
and queue slug. This is generally used in e-mail subjects. """
|
and queue slug. This is generally used in e-mail subjects. """
|
||||||
|
|
||||||
return u"[%s]" % (self.ticket_for_url)
|
return u"[%s]" % self.ticket_for_url
|
||||||
ticket = property(_get_ticket)
|
ticket = property(_get_ticket)
|
||||||
|
|
||||||
def _get_ticket_for_url(self):
|
def _get_ticket_for_url(self):
|
||||||
@ -484,9 +478,11 @@ 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 self.can_be_resolved == False: dep_msg = _(' - Open dependencies')
|
if not self.can_be_resolved:
|
||||||
|
dep_msg = _(' - Open dependencies')
|
||||||
return u'%s%s%s' % (self.get_status_display(), held_msg, dep_msg)
|
return u'%s%s%s' % (self.get_status_display(), held_msg, dep_msg)
|
||||||
get_status = property(_get_status)
|
get_status = property(_get_status)
|
||||||
|
|
||||||
@ -534,7 +530,8 @@ class Ticket(models.Model):
|
|||||||
False = There are non-resolved dependencies
|
False = There are non-resolved dependencies
|
||||||
"""
|
"""
|
||||||
OPEN_STATUSES = (Ticket.OPEN_STATUS, Ticket.REOPENED_STATUS)
|
OPEN_STATUSES = (Ticket.OPEN_STATUS, Ticket.REOPENED_STATUS)
|
||||||
return TicketDependency.objects.filter(ticket=self).filter(depends_on__status__in=OPEN_STATUSES).count() == 0
|
return TicketDependency.objects.filter(ticket=self).filter(
|
||||||
|
depends_on__status__in=OPEN_STATUSES).count() == 0
|
||||||
can_be_resolved = property(_can_be_resolved)
|
can_be_resolved = property(_can_be_resolved)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -547,7 +544,7 @@ class Ticket(models.Model):
|
|||||||
return '%s %s' % (self.id, self.title)
|
return '%s %s' % (self.id, self.title)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return ('helpdesk:view', (self.id,))
|
return 'helpdesk:view', (self.id,)
|
||||||
get_absolute_url = models.permalink(get_absolute_url)
|
get_absolute_url = models.permalink(get_absolute_url)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
@ -562,8 +559,8 @@ class Ticket(models.Model):
|
|||||||
|
|
||||||
super(Ticket, self).save(*args, **kwargs)
|
super(Ticket, self).save(*args, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@staticmethod
|
||||||
def queue_and_id_from_query(klass, query):
|
def queue_and_id_from_query(query):
|
||||||
# Apply the opposite logic here compared to self._get_ticket_for_url
|
# Apply the opposite logic here compared to self._get_ticket_for_url
|
||||||
# Ensure that queues with '-' in them will work
|
# Ensure that queues with '-' in them will work
|
||||||
parts = query.split('-')
|
parts = query.split('-')
|
||||||
@ -572,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)
|
||||||
|
|
||||||
@ -642,7 +640,7 @@ class FollowUp(models.Model):
|
|||||||
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')
|
||||||
|
|
||||||
@ -768,7 +766,7 @@ class Attachment(models.Model):
|
|||||||
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')
|
||||||
|
|
||||||
@ -785,6 +783,10 @@ class PreSetReply(models.Model):
|
|||||||
When replying to a ticket, the user can select any reply set for the current
|
When replying to a ticket, the user can select any reply set for the current
|
||||||
queue, and the body text is fetched via AJAX.
|
queue, and the body text is fetched via AJAX.
|
||||||
"""
|
"""
|
||||||
|
class Meta:
|
||||||
|
ordering = ('name',)
|
||||||
|
verbose_name = _('Pre-set reply')
|
||||||
|
verbose_name_plural = _('Pre-set replies')
|
||||||
|
|
||||||
queues = models.ManyToManyField(
|
queues = models.ManyToManyField(
|
||||||
Queue,
|
Queue,
|
||||||
@ -807,11 +809,6 @@ class PreSetReply(models.Model):
|
|||||||
'- the current user.'),
|
'- the current user.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ['name',]
|
|
||||||
verbose_name = _('Pre-set reply')
|
|
||||||
verbose_name_plural = _('Pre-set replies')
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s' % self.name
|
return '%s' % self.name
|
||||||
|
|
||||||
@ -831,9 +828,8 @@ class EscalationExclusion(models.Model):
|
|||||||
queues = models.ManyToManyField(
|
queues = models.ManyToManyField(
|
||||||
Queue,
|
Queue,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Leave blank for this exclusion to be applied to all '
|
help_text=_('Leave blank for this exclusion to be applied to all queues, '
|
||||||
'queues, or select those queues you wish to exclude with this '
|
'or select those queues you wish to exclude with this entry.'),
|
||||||
'entry.'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
@ -894,8 +890,7 @@ class EmailTemplate(models.Model):
|
|||||||
|
|
||||||
html = models.TextField(
|
html = models.TextField(
|
||||||
_('HTML'),
|
_('HTML'),
|
||||||
help_text=_('The same context is available here as in plain_text, '
|
help_text=_('The same context is available here as in plain_text, above.'),
|
||||||
'above.'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
locale = models.CharField(
|
locale = models.CharField(
|
||||||
@ -910,7 +905,7 @@ class EmailTemplate(models.Model):
|
|||||||
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')
|
||||||
|
|
||||||
@ -939,12 +934,12 @@ class KBCategory(models.Model):
|
|||||||
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')
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return ('kb_category', (), {'slug': self.slug})
|
return 'kb_category', (), {'slug': self.slug}
|
||||||
get_absolute_url = models.permalink(get_absolute_url)
|
get_absolute_url = models.permalink(get_absolute_url)
|
||||||
|
|
||||||
|
|
||||||
@ -986,8 +981,7 @@ class KBItem(models.Model):
|
|||||||
|
|
||||||
last_updated = models.DateTimeField(
|
last_updated = models.DateTimeField(
|
||||||
_('Last Updated'),
|
_('Last Updated'),
|
||||||
help_text=_('The date on which this question was most recently '
|
help_text=_('The date on which this question was most recently changed.'),
|
||||||
'changed.'),
|
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1007,12 +1001,12 @@ 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')
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return ('helpdesk:kb_item', (self.id,))
|
return 'helpdesk:kb_item', (self.id,)
|
||||||
get_absolute_url = models.permalink(get_absolute_url)
|
get_absolute_url = models.permalink(get_absolute_url)
|
||||||
|
|
||||||
|
|
||||||
@ -1076,7 +1070,8 @@ class UserSettings(models.Model):
|
|||||||
|
|
||||||
settings_pickled = models.TextField(
|
settings_pickled = models.TextField(
|
||||||
_('Settings Dictionary'),
|
_('Settings Dictionary'),
|
||||||
help_text=_('This is a base64-encoded representation of a pickled Python dictionary. Do not change this field via the admin.'),
|
help_text=_('This is a base64-encoded representation of a pickled Python dictionary. '
|
||||||
|
'Do not change this field via the admin.'),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
@ -1125,15 +1120,7 @@ def create_usersettings(sender, instance, created, **kwargs):
|
|||||||
if created:
|
if created:
|
||||||
UserSettings.objects.create(user=instance, settings=DEFAULT_USER_SETTINGS)
|
UserSettings.objects.create(user=instance, settings=DEFAULT_USER_SETTINGS)
|
||||||
|
|
||||||
try:
|
|
||||||
# Connecting via settings.AUTH_USER_MODEL (string) fails in Django < 1.7. We need the actual model there.
|
|
||||||
# https://docs.djangoproject.com/en/1.7/topics/auth/customizing/#referencing-the-user-model
|
|
||||||
if VERSION < (1, 7):
|
|
||||||
raise ValueError
|
|
||||||
models.signals.post_save.connect(create_usersettings, sender=settings.AUTH_USER_MODEL)
|
models.signals.post_save.connect(create_usersettings, sender=settings.AUTH_USER_MODEL)
|
||||||
except:
|
|
||||||
signal_user = get_user_model()
|
|
||||||
models.signals.post_save.connect(create_usersettings, sender=signal_user)
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
@ -1143,12 +1130,15 @@ class IgnoreEmail(models.Model):
|
|||||||
processing IMAP and POP3 mailboxes, eg mails from postmaster or from
|
processing IMAP and POP3 mailboxes, eg mails from postmaster or from
|
||||||
known trouble-makers.
|
known trouble-makers.
|
||||||
"""
|
"""
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Ignored e-mail address')
|
||||||
|
verbose_name_plural = _('Ignored e-mail addresses')
|
||||||
|
|
||||||
queues = models.ManyToManyField(
|
queues = models.ManyToManyField(
|
||||||
Queue,
|
Queue,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Leave blank for this e-mail to be ignored on all '
|
help_text=_('Leave blank for this e-mail to be ignored on all queues, '
|
||||||
'queues, or select those queues you wish to ignore this e-mail '
|
'or select those queues you wish to ignore this e-mail for.'),
|
||||||
'for.'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
@ -1174,9 +1164,8 @@ class IgnoreEmail(models.Model):
|
|||||||
_('Save Emails in Mailbox?'),
|
_('Save Emails in Mailbox?'),
|
||||||
blank=True,
|
blank=True,
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_('Do you want to save emails from this address in the '
|
help_text=_('Do you want to save emails from this address in the mailbox? '
|
||||||
'mailbox? If this is unticked, emails from this address will '
|
'If this is unticked, emails from this address will be deleted.'),
|
||||||
'be deleted.'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -1202,18 +1191,14 @@ class IgnoreEmail(models.Model):
|
|||||||
own_parts = self.email_address.split("@")
|
own_parts = self.email_address.split("@")
|
||||||
email_parts = email.split("@")
|
email_parts = email.split("@")
|
||||||
|
|
||||||
if self.email_address == email \
|
if self.email_address == email or \
|
||||||
or own_parts[0] == "*" and own_parts[1] == email_parts[1] \
|
own_parts[0] == "*" and own_parts[1] == email_parts[1] or \
|
||||||
or own_parts[1] == "*" and own_parts[0] == email_parts[0] \
|
own_parts[1] == "*" and own_parts[0] == email_parts[0] or \
|
||||||
or own_parts[0] == "*" and own_parts[1] == "*":
|
own_parts[0] == "*" and own_parts[1] == "*":
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Ignored e-mail address')
|
|
||||||
verbose_name_plural = _('Ignored e-mail addresses')
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class TicketCC(models.Model):
|
class TicketCC(models.Model):
|
||||||
@ -1277,7 +1262,9 @@ class TicketCC(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s for %s' % (self.display, self.ticket.title)
|
return '%s for %s' % (self.display, self.ticket.title)
|
||||||
|
|
||||||
|
|
||||||
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')
|
||||||
|
|
||||||
@ -1290,7 +1277,8 @@ class CustomField(models.Model):
|
|||||||
|
|
||||||
name = models.SlugField(
|
name = models.SlugField(
|
||||||
_('Field Name'),
|
_('Field Name'),
|
||||||
help_text=_('As used in the database and behind the scenes. Must be unique and consist of only lowercase letters with no punctuation.'),
|
help_text=_('As used in the database and behind the scenes. '
|
||||||
|
'Must be unique and consist of only lowercase letters with no punctuation.'),
|
||||||
unique=True,
|
unique=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1346,7 +1334,8 @@ class CustomField(models.Model):
|
|||||||
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, which enforces that the user makes an active choice.'),
|
help_text=_('Only for List: adds an empty first entry to the choices list, '
|
||||||
|
'which enforces that the user makes an active choice.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
list_values = models.TextField(
|
list_values = models.TextField(
|
||||||
@ -1379,14 +1368,15 @@ class CustomField(models.Model):
|
|||||||
|
|
||||||
staff_only = models.BooleanField(
|
staff_only = models.BooleanField(
|
||||||
_('Staff Only?'),
|
_('Staff Only?'),
|
||||||
help_text=_('If this is ticked, then the public submission form will NOT show this field'),
|
help_text=_('If this is ticked, then the public submission form '
|
||||||
|
'will NOT show this field'),
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = CustomFieldManager()
|
objects = CustomFieldManager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s' % (self.name)
|
return '%s' % self.name
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Custom field')
|
verbose_name = _('Custom field')
|
||||||
@ -1423,6 +1413,11 @@ class TicketDependency(models.Model):
|
|||||||
To help enforce this, a helper function `can_be_resolved` on each Ticket instance checks that
|
To help enforce this, a helper function `can_be_resolved` on each Ticket instance checks that
|
||||||
these have all been resolved.
|
these have all been resolved.
|
||||||
"""
|
"""
|
||||||
|
class Meta:
|
||||||
|
unique_together = (('ticket', 'depends_on'),)
|
||||||
|
verbose_name = _('Ticket dependency')
|
||||||
|
verbose_name_plural = _('Ticket dependencies')
|
||||||
|
|
||||||
ticket = models.ForeignKey(
|
ticket = models.ForeignKey(
|
||||||
Ticket,
|
Ticket,
|
||||||
verbose_name=_('Ticket'),
|
verbose_name=_('Ticket'),
|
||||||
@ -1437,8 +1432,3 @@ class TicketDependency(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s / %s' % (self.ticket, self.depends_on)
|
return '%s / %s' % (self.ticket, self.depends_on)
|
||||||
|
|
||||||
class Meta:
|
|
||||||
unique_together = (('ticket', 'depends_on'),)
|
|
||||||
verbose_name = _('Ticket dependency')
|
|
||||||
verbose_name_plural = _('Ticket dependencies')
|
|
||||||
|
@ -11,7 +11,7 @@ try:
|
|||||||
except:
|
except:
|
||||||
DEFAULT_USER_SETTINGS = None
|
DEFAULT_USER_SETTINGS = None
|
||||||
|
|
||||||
if type(DEFAULT_USER_SETTINGS) != type(dict()):
|
if not isinstance(DEFAULT_USER_SETTINGS, dict):
|
||||||
DEFAULT_USER_SETTINGS = {
|
DEFAULT_USER_SETTINGS = {
|
||||||
'use_email_as_submitter': True,
|
'use_email_as_submitter': True,
|
||||||
'email_on_ticket_assign': True,
|
'email_on_ticket_assign': True,
|
||||||
@ -24,9 +24,14 @@ if type(DEFAULT_USER_SETTINGS) != type(dict()):
|
|||||||
|
|
||||||
HAS_TAG_SUPPORT = False
|
HAS_TAG_SUPPORT = False
|
||||||
|
|
||||||
''' generic options - visible on all pages '''
|
##########################################
|
||||||
|
# generic options - visible on all pages #
|
||||||
|
##########################################
|
||||||
|
|
||||||
# redirect to login page instead of the default homepage when users visits "/"?
|
# redirect to login page instead of the default homepage when users visits "/"?
|
||||||
HELPDESK_REDIRECT_TO_LOGIN_BY_DEFAULT = getattr(settings, 'HELPDESK_REDIRECT_TO_LOGIN_BY_DEFAULT', False)
|
HELPDESK_REDIRECT_TO_LOGIN_BY_DEFAULT = getattr(settings,
|
||||||
|
'HELPDESK_REDIRECT_TO_LOGIN_BY_DEFAULT',
|
||||||
|
False)
|
||||||
|
|
||||||
# show knowledgebase links?
|
# show knowledgebase links?
|
||||||
HELPDESK_KB_ENABLED = getattr(settings, 'HELPDESK_KB_ENABLED', True)
|
HELPDESK_KB_ENABLED = getattr(settings, 'HELPDESK_KB_ENABLED', True)
|
||||||
@ -34,14 +39,20 @@ HELPDESK_KB_ENABLED = getattr(settings, 'HELPDESK_KB_ENABLED', True)
|
|||||||
# show extended navigation by default, to all users, irrespective of staff status?
|
# show extended navigation by default, to all users, irrespective of staff status?
|
||||||
HELPDESK_NAVIGATION_ENABLED = getattr(settings, 'HELPDESK_NAVIGATION_ENABLED', False)
|
HELPDESK_NAVIGATION_ENABLED = getattr(settings, 'HELPDESK_NAVIGATION_ENABLED', False)
|
||||||
|
|
||||||
# use public CDNs to serve jquery and other javascript by default? otherwise, use built-in static copy
|
# use public CDNs to serve jquery and other javascript by default?
|
||||||
|
# otherwise, use built-in static copy
|
||||||
HELPDESK_USE_CDN = getattr(settings, 'HELPDESK_USE_CDN', False)
|
HELPDESK_USE_CDN = getattr(settings, 'HELPDESK_USE_CDN', False)
|
||||||
|
|
||||||
# show dropdown list of languages that ticket comments can be translated into?
|
# show dropdown list of languages that ticket comments can be translated into?
|
||||||
HELPDESK_TRANSLATE_TICKET_COMMENTS = getattr(settings, 'HELPDESK_TRANSLATE_TICKET_COMMENTS', False)
|
HELPDESK_TRANSLATE_TICKET_COMMENTS = getattr(settings,
|
||||||
|
'HELPDESK_TRANSLATE_TICKET_COMMENTS',
|
||||||
|
False)
|
||||||
|
|
||||||
# list of languages to offer. if set to false, all default google translate languages will be shown.
|
# list of languages to offer. if set to false,
|
||||||
HELPDESK_TRANSLATE_TICKET_COMMENTS_LANG = getattr(settings, 'HELPDESK_TRANSLATE_TICKET_COMMENTS_LANG', ["en", "de", "fr", "it", "ru"])
|
# all default google translate languages will be shown.
|
||||||
|
HELPDESK_TRANSLATE_TICKET_COMMENTS_LANG = getattr(settings,
|
||||||
|
'HELPDESK_TRANSLATE_TICKET_COMMENTS_LANG',
|
||||||
|
["en", "de", "fr", "it", "ru"])
|
||||||
|
|
||||||
# show link to 'change password' on 'User Settings' page?
|
# show link to 'change password' on 'User Settings' page?
|
||||||
HELPDESK_SHOW_CHANGE_PASSWORD = getattr(settings, 'HELPDESK_SHOW_CHANGE_PASSWORD', False)
|
HELPDESK_SHOW_CHANGE_PASSWORD = getattr(settings, 'HELPDESK_SHOW_CHANGE_PASSWORD', False)
|
||||||
@ -50,10 +61,15 @@ HELPDESK_SHOW_CHANGE_PASSWORD = getattr(settings, 'HELPDESK_SHOW_CHANGE_PASSWORD
|
|||||||
HELPDESK_FOLLOWUP_MOD = getattr(settings, 'HELPDESK_FOLLOWUP_MOD', False)
|
HELPDESK_FOLLOWUP_MOD = getattr(settings, 'HELPDESK_FOLLOWUP_MOD', False)
|
||||||
|
|
||||||
# auto-subscribe user to ticket if (s)he responds to a ticket?
|
# auto-subscribe user to ticket if (s)he responds to a ticket?
|
||||||
HELPDESK_AUTO_SUBSCRIBE_ON_TICKET_RESPONSE = getattr(settings, 'HELPDESK_AUTO_SUBSCRIBE_ON_TICKET_RESPONSE', False)
|
HELPDESK_AUTO_SUBSCRIBE_ON_TICKET_RESPONSE = getattr(settings,
|
||||||
|
'HELPDESK_AUTO_SUBSCRIBE_ON_TICKET_RESPONSE',
|
||||||
|
False)
|
||||||
|
|
||||||
|
|
||||||
''' options for public pages '''
|
############################
|
||||||
|
# options for public pages #
|
||||||
|
############################
|
||||||
|
|
||||||
# show 'view a ticket' section on public page?
|
# show 'view a ticket' section on public page?
|
||||||
HELPDESK_VIEW_A_TICKET_PUBLIC = getattr(settings, 'HELPDESK_VIEW_A_TICKET_PUBLIC', True)
|
HELPDESK_VIEW_A_TICKET_PUBLIC = getattr(settings, 'HELPDESK_VIEW_A_TICKET_PUBLIC', True)
|
||||||
|
|
||||||
@ -61,17 +77,25 @@ HELPDESK_VIEW_A_TICKET_PUBLIC = getattr(settings, 'HELPDESK_VIEW_A_TICKET_PUBLIC
|
|||||||
HELPDESK_SUBMIT_A_TICKET_PUBLIC = getattr(settings, 'HELPDESK_SUBMIT_A_TICKET_PUBLIC', True)
|
HELPDESK_SUBMIT_A_TICKET_PUBLIC = getattr(settings, 'HELPDESK_SUBMIT_A_TICKET_PUBLIC', True)
|
||||||
|
|
||||||
|
|
||||||
|
###################################
|
||||||
|
# options for update_ticket views #
|
||||||
|
###################################
|
||||||
|
|
||||||
''' options for update_ticket views '''
|
# allow non-staff users to interact with tickets?
|
||||||
# allow non-staff users to interact with tickets? this will also change how 'staff_member_required'
|
# this will also change how 'staff_member_required'
|
||||||
# in staff.py will be defined.
|
# in staff.py will be defined.
|
||||||
HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE = getattr(settings, 'HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE', False)
|
HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE = getattr(settings,
|
||||||
|
'HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE',
|
||||||
|
False)
|
||||||
|
|
||||||
# show edit buttons in ticket follow ups.
|
# show edit buttons in ticket follow ups.
|
||||||
HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP = getattr(settings, 'HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP', True)
|
HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP = getattr(settings,
|
||||||
|
'HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP',
|
||||||
|
True)
|
||||||
|
|
||||||
# show delete buttons in ticket follow ups if user is 'superuser'
|
# show delete buttons in ticket follow ups if user is 'superuser'
|
||||||
HELPDESK_SHOW_DELETE_BUTTON_SUPERUSER_FOLLOW_UP = getattr(settings, 'HELPDESK_SHOW_DELETE_BUTTON_SUPERUSER_FOLLOW_UP', False)
|
HELPDESK_SHOW_DELETE_BUTTON_SUPERUSER_FOLLOW_UP = getattr(
|
||||||
|
settings, 'HELPDESK_SHOW_DELETE_BUTTON_SUPERUSER_FOLLOW_UP', False)
|
||||||
|
|
||||||
# make all updates public by default? this will hide the 'is this update public' checkbox
|
# make all updates public by default? this will hide the 'is this update public' checkbox
|
||||||
HELPDESK_UPDATE_PUBLIC_DEFAULT = getattr(settings, 'HELPDESK_UPDATE_PUBLIC_DEFAULT', False)
|
HELPDESK_UPDATE_PUBLIC_DEFAULT = getattr(settings, 'HELPDESK_UPDATE_PUBLIC_DEFAULT', False)
|
||||||
@ -82,21 +106,28 @@ HELPDESK_STAFF_ONLY_TICKET_OWNERS = getattr(settings, 'HELPDESK_STAFF_ONLY_TICKE
|
|||||||
# only show staff users in ticket cc drop-down
|
# only show staff users in ticket cc drop-down
|
||||||
HELPDESK_STAFF_ONLY_TICKET_CC = getattr(settings, 'HELPDESK_STAFF_ONLY_TICKET_CC', False)
|
HELPDESK_STAFF_ONLY_TICKET_CC = getattr(settings, 'HELPDESK_STAFF_ONLY_TICKET_CC', False)
|
||||||
|
|
||||||
|
|
||||||
# allow the subject to have a configurable template.
|
# allow the subject to have a configurable template.
|
||||||
HELPDESK_EMAIL_SUBJECT_TEMPLATE = getattr(settings, 'HELPDESK_EMAIL_SUBJECT_TEMPLATE', "{{ ticket.ticket }} {{ ticket.title|safe }} %(subject)s")
|
HELPDESK_EMAIL_SUBJECT_TEMPLATE = getattr(
|
||||||
|
settings, 'HELPDESK_EMAIL_SUBJECT_TEMPLATE',
|
||||||
|
"{{ ticket.ticket }} {{ ticket.title|safe }} %(subject)s")
|
||||||
|
|
||||||
# default fallback locale when queue locale not found
|
# default fallback locale when queue locale not found
|
||||||
HELPDESK_EMAIL_FALLBACK_LOCALE = getattr(settings, 'HELPDESK_EMAIL_FALLBACK_LOCALE', 'en')
|
HELPDESK_EMAIL_FALLBACK_LOCALE = getattr(settings, 'HELPDESK_EMAIL_FALLBACK_LOCALE', 'en')
|
||||||
|
|
||||||
|
|
||||||
''' options for staff.create_ticket view '''
|
########################################
|
||||||
|
# options for staff.create_ticket view #
|
||||||
|
########################################
|
||||||
|
|
||||||
# hide the 'assigned to' / 'Case owner' field from the 'create_ticket' view?
|
# hide the 'assigned to' / 'Case owner' field from the 'create_ticket' view?
|
||||||
HELPDESK_CREATE_TICKET_HIDE_ASSIGNED_TO = getattr(settings, 'HELPDESK_CREATE_TICKET_HIDE_ASSIGNED_TO', False)
|
HELPDESK_CREATE_TICKET_HIDE_ASSIGNED_TO = getattr(
|
||||||
|
settings, 'HELPDESK_CREATE_TICKET_HIDE_ASSIGNED_TO', False)
|
||||||
|
|
||||||
|
|
||||||
|
#################
|
||||||
|
# email options #
|
||||||
|
#################
|
||||||
|
|
||||||
''' email options '''
|
|
||||||
# default Queue email submission settings
|
# default Queue email submission settings
|
||||||
QUEUE_EMAIL_BOX_TYPE = getattr(settings, 'QUEUE_EMAIL_BOX_TYPE', None)
|
QUEUE_EMAIL_BOX_TYPE = getattr(settings, 'QUEUE_EMAIL_BOX_TYPE', None)
|
||||||
QUEUE_EMAIL_BOX_SSL = getattr(settings, 'QUEUE_EMAIL_BOX_SSL', None)
|
QUEUE_EMAIL_BOX_SSL = getattr(settings, 'QUEUE_EMAIL_BOX_SSL', None)
|
||||||
@ -104,6 +135,6 @@ QUEUE_EMAIL_BOX_HOST = getattr(settings, 'QUEUE_EMAIL_BOX_HOST', None)
|
|||||||
QUEUE_EMAIL_BOX_USER = getattr(settings, 'QUEUE_EMAIL_BOX_USER', None)
|
QUEUE_EMAIL_BOX_USER = getattr(settings, 'QUEUE_EMAIL_BOX_USER', None)
|
||||||
QUEUE_EMAIL_BOX_PASSWORD = getattr(settings, 'QUEUE_EMAIL_BOX_PASSWORD', None)
|
QUEUE_EMAIL_BOX_PASSWORD = getattr(settings, 'QUEUE_EMAIL_BOX_PASSWORD', None)
|
||||||
|
|
||||||
|
|
||||||
# only allow users to access queues that they are members of?
|
# only allow users to access queues that they are members of?
|
||||||
HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION = getattr(settings, 'HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION', False)
|
HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION = getattr(
|
||||||
|
settings, 'HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION', False)
|
||||||
|
@ -17,6 +17,7 @@ Assuming 'food' = 'pizza' and 'best_foods' = ['pizza', 'pie', 'cake]:
|
|||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
|
|
||||||
|
|
||||||
def in_list(value, arg):
|
def in_list(value, arg):
|
||||||
return value in (arg or [])
|
return value in (arg or [])
|
||||||
|
|
||||||
|
@ -4,17 +4,19 @@ 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 django.template import Library
|
from django.template import Library
|
||||||
from helpdesk import settings as helpdesk_settings_config
|
from helpdesk import settings as helpdesk_settings_config
|
||||||
|
|
||||||
|
|
||||||
def load_helpdesk_settings(request):
|
def load_helpdesk_settings(request):
|
||||||
try:
|
try:
|
||||||
return helpdesk_settings_config
|
return helpdesk_settings_config
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import sys
|
import sys
|
||||||
print >> sys.stderr, "'load_helpdesk_settings' template tag (django-helpdesk) crashed with following error:"
|
print("'load_helpdesk_settings' template tag (django-helpdesk) crashed with following error:",
|
||||||
print >> sys.stderr, e
|
file=sys.stderr)
|
||||||
|
print(e, file=sys.stderr)
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
@ -20,18 +20,6 @@ from django.utils.safestring import mark_safe
|
|||||||
from helpdesk.models import Ticket
|
from helpdesk.models import Ticket
|
||||||
|
|
||||||
|
|
||||||
class ReverseProxy:
|
|
||||||
def __init__(self, sequence):
|
|
||||||
self.sequence = sequence
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
length = len(self.sequence)
|
|
||||||
i = length
|
|
||||||
while i > 0:
|
|
||||||
i = i - 1
|
|
||||||
yield self.sequence[i]
|
|
||||||
|
|
||||||
|
|
||||||
def num_to_link(text):
|
def num_to_link(text):
|
||||||
if text == '':
|
if text == '':
|
||||||
return text
|
return text
|
||||||
@ -40,9 +28,7 @@ def num_to_link(text):
|
|||||||
for match in re.finditer(r"(?:[^&]|\b|^)#(\d+)\b", text):
|
for match in re.finditer(r"(?:[^&]|\b|^)#(\d+)\b", text):
|
||||||
matches.append(match)
|
matches.append(match)
|
||||||
|
|
||||||
for match in ReverseProxy(matches):
|
for match in reversed(matches):
|
||||||
start = match.start()
|
|
||||||
end = match.end()
|
|
||||||
number = match.groups()[0]
|
number = match.groups()[0]
|
||||||
url = reverse('helpdesk:view', args=[number])
|
url = reverse('helpdesk:view', args=[number])
|
||||||
try:
|
try:
|
||||||
@ -52,7 +38,8 @@ def num_to_link(text):
|
|||||||
|
|
||||||
if ticket:
|
if ticket:
|
||||||
style = ticket.get_status_display()
|
style = ticket.get_status_display()
|
||||||
text = "%s <a href='%s' class='ticket_link_status ticket_link_status_%s'>#%s</a>%s" % (text[:match.start()], url, style, match.groups()[0], text[match.end():])
|
text = "%s <a href='%s' class='ticket_link_status ticket_link_status_%s'>#%s</a>%s" % (
|
||||||
|
text[:match.start()], url, style, match.groups()[0], text[match.end():])
|
||||||
return mark_safe(text)
|
return mark_safe(text)
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
@ -12,6 +12,7 @@ templatetags/admin_url.py - Very simple template tag allow linking to the
|
|||||||
from django import template
|
from django import template
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
|
||||||
def user_admin_url(action):
|
def user_admin_url(action):
|
||||||
user = get_user_model()
|
user = get_user_model()
|
||||||
try:
|
try:
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import sys
|
import sys
|
||||||
try:
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
except ImportError:
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
else:
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
from helpdesk.models import Queue, CustomField, Ticket
|
from helpdesk.models import Queue, Ticket
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.core import mail
|
|
||||||
from django.test.client import Client
|
from django.test.client import Client
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
|
||||||
class PublicActionsTestCase(TestCase):
|
class PublicActionsTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests for public actions:
|
Tests for public actions:
|
||||||
@ -11,17 +11,28 @@ 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.
|
||||||
"""
|
"""
|
||||||
self.queue = Queue.objects.create(title='Queue 1', slug='q', allow_public_submission=True, new_ticket_cc='new.public@example.com', updated_ticket_cc='update.public@example.com')
|
self.queue = Queue.objects.create(title='Queue 1',
|
||||||
self.ticket = Ticket.objects.create(title='Test Ticket', queue=self.queue, submitter_email='test.submitter@example.com', description='This is a test ticket.')
|
slug='q',
|
||||||
|
allow_public_submission=True,
|
||||||
|
new_ticket_cc='new.public@example.com',
|
||||||
|
updated_ticket_cc='update.public@example.com')
|
||||||
|
self.ticket = Ticket.objects.create(title='Test Ticket',
|
||||||
|
queue=self.queue,
|
||||||
|
submitter_email='test.submitter@example.com',
|
||||||
|
description='This is a test ticket.')
|
||||||
|
|
||||||
self.client = Client()
|
self.client = Client()
|
||||||
|
|
||||||
def test_public_view_ticket(self):
|
def test_public_view_ticket(self):
|
||||||
response = self.client.get('%s?ticket=%s&email=%s' % (reverse('helpdesk:public_view'), self.ticket.ticket_for_url, 'test.submitter@example.com'))
|
response = self.client.get('%s?ticket=%s&email=%s' % (
|
||||||
|
reverse('helpdesk:public_view'),
|
||||||
|
self.ticket.ticket_for_url,
|
||||||
|
'test.submitter@example.com'))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertTemplateNotUsed(response, 'helpdesk/public_view_form.html')
|
self.assertTemplateNotUsed(response, 'helpdesk/public_view_form.html')
|
||||||
|
|
||||||
@ -38,7 +49,10 @@ class PublicActionsTestCase(TestCase):
|
|||||||
|
|
||||||
current_followups = ticket.followup_set.all().count()
|
current_followups = ticket.followup_set.all().count()
|
||||||
|
|
||||||
response = self.client.get('%s?ticket=%s&email=%s&close' % (reverse('helpdesk:public_view'), ticket.ticket_for_url, 'test.submitter@example.com'))
|
response = self.client.get('%s?ticket=%s&email=%s&close' % (
|
||||||
|
reverse('helpdesk:public_view'),
|
||||||
|
ticket.ticket_for_url,
|
||||||
|
'test.submitter@example.com'))
|
||||||
|
|
||||||
ticket = Ticket.objects.get(id=self.ticket.id)
|
ticket = Ticket.objects.get(id=self.ticket.id)
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from helpdesk.models import Ticket, Queue
|
from helpdesk.models import Queue
|
||||||
from helpdesk.tests.helpers import get_staff_user, reload_urlconf
|
from helpdesk.tests.helpers import get_staff_user
|
||||||
|
|
||||||
|
|
||||||
class TestSavingSharedQuery(TestCase):
|
class TestSavingSharedQuery(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -15,12 +16,15 @@ class TestSavingSharedQuery(TestCase):
|
|||||||
url = reverse('helpdesk:savequery')
|
url = reverse('helpdesk:savequery')
|
||||||
self.client.login(username=get_staff_user().get_username(),
|
self.client.login(username=get_staff_user().get_username(),
|
||||||
password='password')
|
password='password')
|
||||||
response = self.client.post(url,
|
response = self.client.post(
|
||||||
data={'title': 'ticket on my queue',
|
url,
|
||||||
|
data={
|
||||||
|
'title': 'ticket on my queue',
|
||||||
'queue': self.q,
|
'queue': self.q,
|
||||||
'shared': 'on',
|
'shared': 'on',
|
||||||
'query_encoded':'KGRwMApWZmlsdGVyaW5nCnAxCihkcDIKVnN0YXR1c19faW4KcDMKKGxwNApJMQphSTIKYUkzCmFzc1Zzb3J0aW5nCnA1ClZjcmVhdGVkCnA2CnMu'})
|
'query_encoded':
|
||||||
|
'KGRwMApWZmlsdGVyaW5nCnAxCihkcDIKVnN0YXR1c19faW4KcDMKKG'
|
||||||
|
'xwNApJMQphSTIKYUkzCmFzc1Zzb3J0aW5nCnA1ClZjcmVhdGVkCnA2CnMu'
|
||||||
|
})
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertTrue('tickets/?saved_query=1' in response.url)
|
self.assertTrue('tickets/?saved_query=1' in response.url)
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ from django.core.urlresolvers import reverse
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from helpdesk.models import Ticket, Queue
|
from helpdesk.models import Ticket, Queue
|
||||||
|
|
||||||
|
|
||||||
class TestKBDisabled(TestCase):
|
class TestKBDisabled(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
q = Queue(title='Q1', slug='q1')
|
q = Queue(title='Q1', slug='q1')
|
||||||
@ -14,18 +15,15 @@ class TestKBDisabled(TestCase):
|
|||||||
|
|
||||||
def test_ticket_by_id(self):
|
def test_ticket_by_id(self):
|
||||||
"""Can a ticket be looked up by its ID"""
|
"""Can a ticket be looked up by its ID"""
|
||||||
from django.core.urlresolvers import NoReverseMatch
|
|
||||||
|
|
||||||
# get the ticket from models
|
# get the ticket from models
|
||||||
t = Ticket.objects.get(id=self.ticket.id)
|
t = Ticket.objects.get(id=self.ticket.id)
|
||||||
self.assertEqual(t.title, self.ticket.title)
|
self.assertEqual(t.title, self.ticket.title)
|
||||||
|
|
||||||
def test_ticket_by_link(self):
|
def test_ticket_by_link(self):
|
||||||
"""Can a ticket be looked up by its link from (eg) an email"""
|
"""Can a ticket be looked up by its link from (eg) an email"""
|
||||||
# Work out the link which would have been inserted into the email
|
# Instead of using the ticket_for_url link,
|
||||||
link = self.ticket.ticket_url
|
# we will exercise 'reverse' to lookup/build the URL
|
||||||
# however instead of using that link, we will exercise 'reverse'
|
# from the ticket info we have
|
||||||
# to lookup/build the URL from the ticket info we have
|
|
||||||
# http://example.com/helpdesk/view/?ticket=q1-1&email=None
|
# http://example.com/helpdesk/view/?ticket=q1-1&email=None
|
||||||
response = self.client.get(reverse('helpdesk:public_view'),
|
response = self.client.get(reverse('helpdesk:public_view'),
|
||||||
{'ticket': self.ticket.ticket_for_url,
|
{'ticket': self.ticket.ticket_for_url,
|
||||||
|
@ -14,8 +14,18 @@ class TicketBasicsTestCase(TestCase):
|
|||||||
fixtures = ['emailtemplate.json']
|
fixtures = ['emailtemplate.json']
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.queue_public = Queue.objects.create(title='Queue 1', slug='q1', allow_public_submission=True, new_ticket_cc='new.public@example.com', updated_ticket_cc='update.public@example.com')
|
self.queue_public = Queue.objects.create(
|
||||||
self.queue_private = Queue.objects.create(title='Queue 2', slug='q2', allow_public_submission=False, new_ticket_cc='new.private@example.com', updated_ticket_cc='update.private@example.com')
|
title='Queue 1',
|
||||||
|
slug='q1',
|
||||||
|
allow_public_submission=True,
|
||||||
|
new_ticket_cc='new.public@example.com',
|
||||||
|
updated_ticket_cc='update.public@example.com')
|
||||||
|
self.queue_private = Queue.objects.create(
|
||||||
|
title='Queue 2',
|
||||||
|
slug='q2',
|
||||||
|
allow_public_submission=False,
|
||||||
|
new_ticket_cc='new.private@example.com',
|
||||||
|
updated_ticket_cc='update.private@example.com')
|
||||||
|
|
||||||
self.ticket_data = {
|
self.ticket_data = {
|
||||||
'title': 'Test Ticket',
|
'title': 'Test Ticket',
|
||||||
@ -31,7 +41,6 @@ class TicketBasicsTestCase(TestCase):
|
|||||||
self.assertEqual(ticket.ticket_for_url, "q1-%s" % ticket.id)
|
self.assertEqual(ticket.ticket_for_url, "q1-%s" % ticket.id)
|
||||||
self.assertEqual(email_count, len(mail.outbox))
|
self.assertEqual(email_count, len(mail.outbox))
|
||||||
|
|
||||||
|
|
||||||
def test_create_ticket_public(self):
|
def test_create_ticket_public(self):
|
||||||
email_count = len(mail.outbox)
|
email_count = len(mail.outbox)
|
||||||
|
|
||||||
@ -49,7 +58,7 @@ class TicketBasicsTestCase(TestCase):
|
|||||||
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]
|
||||||
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
|
||||||
@ -77,8 +86,19 @@ class TicketBasicsTestCase(TestCase):
|
|||||||
|
|
||||||
def test_create_ticket_customfields(self):
|
def test_create_ticket_customfields(self):
|
||||||
email_count = len(mail.outbox)
|
email_count = len(mail.outbox)
|
||||||
queue_custom = Queue.objects.create(title='Queue 3', slug='q3', allow_public_submission=True, updated_ticket_cc='update.custom@example.com')
|
queue_custom = Queue.objects.create(
|
||||||
custom_field_1 = CustomField.objects.create(name='textfield', label='Text Field', data_type='varchar', max_length=100, ordering=10, required=False, staff_only=False)
|
title='Queue 3',
|
||||||
|
slug='q3',
|
||||||
|
allow_public_submission=True,
|
||||||
|
updated_ticket_cc='update.custom@example.com')
|
||||||
|
custom_field_1 = CustomField.objects.create(
|
||||||
|
name='textfield',
|
||||||
|
label='Text Field',
|
||||||
|
data_type='varchar',
|
||||||
|
max_length=100,
|
||||||
|
ordering=10,
|
||||||
|
required=False,
|
||||||
|
staff_only=False)
|
||||||
post_data = {
|
post_data = {
|
||||||
'queue': queue_custom.id,
|
'queue': queue_custom.id,
|
||||||
'title': 'Ticket with custom text field',
|
'title': 'Ticket with custom text field',
|
||||||
@ -93,7 +113,7 @@ class TicketBasicsTestCase(TestCase):
|
|||||||
custom_field_1.delete()
|
custom_field_1.delete()
|
||||||
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
|
||||||
|
@ -7,21 +7,18 @@ urls.py - Mapping of URL's to our various views. Note we always used NAMED
|
|||||||
views for simplicity in linking later on.
|
views for simplicity in linking later on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf.urls import url
|
||||||
import django
|
|
||||||
if django.get_version().startswith("1.3"):
|
|
||||||
from django.conf.urls.defaults import *
|
|
||||||
else:
|
|
||||||
from django.conf.urls import *
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib.auth import views as auth_views
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from helpdesk import settings as helpdesk_settings
|
from helpdesk import settings as helpdesk_settings
|
||||||
from helpdesk.views import feeds, staff, public, api, kb
|
from helpdesk.views import feeds, staff, public, api, kb
|
||||||
from django.contrib.auth import views as auth_views
|
|
||||||
|
|
||||||
from django.views.generic import TemplateView
|
|
||||||
class DirectTemplateView(TemplateView):
|
class DirectTemplateView(TemplateView):
|
||||||
extra_context = None
|
extra_context = None
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(self.__class__, self).get_context_data(**kwargs)
|
context = super(self.__class__, self).get_context_data(**kwargs)
|
||||||
if self.extra_context is not None:
|
if self.extra_context is not None:
|
||||||
|
@ -11,16 +11,10 @@ The API documentation can be accessed by visiting http://helpdesk/api/help/
|
|||||||
through templates/helpdesk/help_api.html.
|
through templates/helpdesk/help_api.html.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django import forms
|
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate
|
||||||
try:
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
User = get_user_model()
|
|
||||||
except ImportError:
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.template import loader, Context
|
|
||||||
import simplejson
|
import simplejson
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
@ -35,6 +29,8 @@ from helpdesk.models import Ticket, Queue, FollowUp
|
|||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
STATUS_OK = 200
|
STATUS_OK = 200
|
||||||
|
|
||||||
STATUS_ERROR = 400
|
STATUS_ERROR = 400
|
||||||
@ -61,7 +57,9 @@ def api(request, method):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
warnings.warn("django-helpdesk API will be removed in January 2016. See https://github.com/django-helpdesk/django-helpdesk/issues/198 for details.", category=DeprecationWarning)
|
warnings.warn("django-helpdesk API will be removed in January 2016. "
|
||||||
|
"See https://github.com/django-helpdesk/django-helpdesk/issues/198 for details.",
|
||||||
|
category=DeprecationWarning)
|
||||||
|
|
||||||
if method == 'help':
|
if method == 'help':
|
||||||
return render(request, template_name='helpdesk/help_api.html')
|
return render(request, template_name='helpdesk/help_api.html')
|
||||||
@ -114,7 +112,6 @@ class API:
|
|||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
self.request = request
|
self.request = request
|
||||||
|
|
||||||
|
|
||||||
def api_public_create_ticket(self):
|
def api_public_create_ticket(self):
|
||||||
form = TicketForm(self.request.POST)
|
form = TicketForm(self.request.POST)
|
||||||
form.fields['queue'].choices = [[q.id, q.title] for q in Queue.objects.all()]
|
form.fields['queue'].choices = [[q.id, q.title] for q in Queue.objects.all()]
|
||||||
@ -126,10 +123,11 @@ class API:
|
|||||||
else:
|
else:
|
||||||
return api_return(STATUS_ERROR, text=form.errors.as_text())
|
return api_return(STATUS_ERROR, text=form.errors.as_text())
|
||||||
|
|
||||||
|
|
||||||
def api_public_list_queues(self):
|
def api_public_list_queues(self):
|
||||||
return api_return(STATUS_OK, simplejson.dumps([{"id": "%s" % q.id, "title": "%s" % q.title} for q in Queue.objects.all()]), json=True)
|
return api_return(STATUS_OK, simplejson.dumps([
|
||||||
|
{"id": "%s" % q.id, "title": "%s" % q.title}
|
||||||
|
for q in Queue.objects.all()
|
||||||
|
]), json=True)
|
||||||
|
|
||||||
def api_public_find_user(self):
|
def api_public_find_user(self):
|
||||||
username = self.request.POST.get('username', False)
|
username = self.request.POST.get('username', False)
|
||||||
@ -141,7 +139,6 @@ class API:
|
|||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
return api_return(STATUS_ERROR, "Invalid username provided")
|
return api_return(STATUS_ERROR, "Invalid username provided")
|
||||||
|
|
||||||
|
|
||||||
def api_public_delete_ticket(self):
|
def api_public_delete_ticket(self):
|
||||||
if not self.request.POST.get('confirm', False):
|
if not self.request.POST.get('confirm', False):
|
||||||
return api_return(STATUS_ERROR, "No confirmation provided")
|
return api_return(STATUS_ERROR, "No confirmation provided")
|
||||||
@ -155,7 +152,6 @@ class API:
|
|||||||
|
|
||||||
return api_return(STATUS_OK)
|
return api_return(STATUS_OK)
|
||||||
|
|
||||||
|
|
||||||
def api_public_hold_ticket(self):
|
def api_public_hold_ticket(self):
|
||||||
try:
|
try:
|
||||||
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
||||||
@ -167,7 +163,6 @@ class API:
|
|||||||
|
|
||||||
return api_return(STATUS_OK)
|
return api_return(STATUS_OK)
|
||||||
|
|
||||||
|
|
||||||
def api_public_unhold_ticket(self):
|
def api_public_unhold_ticket(self):
|
||||||
try:
|
try:
|
||||||
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
||||||
@ -179,7 +174,6 @@ class API:
|
|||||||
|
|
||||||
return api_return(STATUS_OK)
|
return api_return(STATUS_OK)
|
||||||
|
|
||||||
|
|
||||||
def api_public_add_followup(self):
|
def api_public_add_followup(self):
|
||||||
try:
|
try:
|
||||||
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
||||||
@ -264,7 +258,6 @@ class API:
|
|||||||
|
|
||||||
return api_return(STATUS_OK)
|
return api_return(STATUS_OK)
|
||||||
|
|
||||||
|
|
||||||
def api_public_resolve(self):
|
def api_public_resolve(self):
|
||||||
try:
|
try:
|
||||||
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
||||||
@ -289,7 +282,7 @@ class API:
|
|||||||
context = safe_template_context(ticket)
|
context = safe_template_context(ticket)
|
||||||
context['resolution'] = f.comment
|
context['resolution'] = f.comment
|
||||||
|
|
||||||
subject = '%s %s (Resolved)' % (ticket.ticket, ticket.title)
|
# subject = '%s %s (Resolved)' % (ticket.ticket, ticket.title)
|
||||||
|
|
||||||
messages_sent_to = []
|
messages_sent_to = []
|
||||||
|
|
||||||
@ -324,7 +317,12 @@ class API:
|
|||||||
)
|
)
|
||||||
messages_sent_to.append(ticket.queue.updated_ticket_cc)
|
messages_sent_to.append(ticket.queue.updated_ticket_cc)
|
||||||
|
|
||||||
if ticket.assigned_to and self.request.user != ticket.assigned_to and getattr(ticket.assigned_to.usersettings.settings, 'email_on_ticket_apichange', False) and ticket.assigned_to.email and ticket.assigned_to.email not in messages_sent_to:
|
if ticket.assigned_to and \
|
||||||
|
self.request.user != ticket.assigned_to and \
|
||||||
|
getattr(ticket.assigned_to.usersettings.settings,
|
||||||
|
'email_on_ticket_apichange', False) and \
|
||||||
|
ticket.assigned_to.email and \
|
||||||
|
ticket.assigned_to.email not in messages_sent_to:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
'resolved_resolved',
|
'resolved_resolved',
|
||||||
context,
|
context,
|
||||||
|
@ -7,11 +7,7 @@ views/feeds.py - A handful of staff-only RSS feeds to provide ticket details
|
|||||||
to feed readers or similar software.
|
to feed readers or similar software.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
User = get_user_model()
|
|
||||||
except ImportError:
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.contrib.syndication.views import Feed
|
from django.contrib.syndication.views import Feed
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
@ -20,6 +16,8 @@ from django.shortcuts import get_object_or_404
|
|||||||
|
|
||||||
from helpdesk.models import Ticket, FollowUp, Queue
|
from helpdesk.models import Ticket, FollowUp, Queue
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class OpenTicketsByUser(Feed):
|
class OpenTicketsByUser(Feed):
|
||||||
title_template = 'helpdesk/rss/ticket_title.html'
|
title_template = 'helpdesk/rss/ticket_title.html'
|
||||||
@ -101,7 +99,7 @@ class UnassignedTickets(Feed):
|
|||||||
|
|
||||||
title = _('Helpdesk: Unassigned Tickets')
|
title = _('Helpdesk: Unassigned Tickets')
|
||||||
description = _('Unassigned Open and Reopened tickets')
|
description = _('Unassigned Open and Reopened tickets')
|
||||||
link = ''#%s?assigned_to=' % reverse('helpdesk:list')
|
link = '' # '%s?assigned_to=' % reverse('helpdesk:list')
|
||||||
|
|
||||||
def items(self, obj):
|
def items(self, obj):
|
||||||
return Ticket.objects.filter(
|
return Ticket.objects.filter(
|
||||||
@ -113,7 +111,6 @@ class UnassignedTickets(Feed):
|
|||||||
def item_pubdate(self, item):
|
def item_pubdate(self, item):
|
||||||
return item.created
|
return item.created
|
||||||
|
|
||||||
|
|
||||||
def item_author_name(self, item):
|
def item_author_name(self, item):
|
||||||
if item.assigned_to:
|
if item.assigned_to:
|
||||||
return item.assigned_to.get_username()
|
return item.assigned_to.get_username()
|
||||||
@ -171,4 +168,3 @@ class OpenTicketsByQueue(Feed):
|
|||||||
return item.assigned_to.get_username()
|
return item.assigned_to.get_username()
|
||||||
else:
|
else:
|
||||||
return _('Unassigned')
|
return _('Unassigned')
|
||||||
|
|
||||||
|
@ -8,12 +8,8 @@ views/kb.py - Public-facing knowledgebase views. The knowledgebase is a
|
|||||||
resolutions to common problems.
|
resolutions to common problems.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
from django.template import RequestContext
|
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
|
|
||||||
from helpdesk import settings as helpdesk_settings
|
from helpdesk import settings as helpdesk_settings
|
||||||
from helpdesk.models import KBCategory, KBItem
|
from helpdesk.models import KBCategory, KBItem
|
||||||
@ -22,8 +18,7 @@ from helpdesk.models import KBCategory, KBItem
|
|||||||
def index(request):
|
def index(request):
|
||||||
category_list = KBCategory.objects.all()
|
category_list = KBCategory.objects.all()
|
||||||
# TODO: It'd be great to have a list of most popular items here.
|
# TODO: It'd be great to have a list of most popular items here.
|
||||||
return render(request, template_name='helpdesk/kb_index.html',
|
return render(request, 'helpdesk/kb_index.html', {
|
||||||
context = {
|
|
||||||
'kb_categories': category_list,
|
'kb_categories': category_list,
|
||||||
'helpdesk_settings': helpdesk_settings,
|
'helpdesk_settings': helpdesk_settings,
|
||||||
})
|
})
|
||||||
@ -32,8 +27,7 @@ def index(request):
|
|||||||
def category(request, slug):
|
def category(request, slug):
|
||||||
category = get_object_or_404(KBCategory, slug__iexact=slug)
|
category = get_object_or_404(KBCategory, slug__iexact=slug)
|
||||||
items = category.kbitem_set.all()
|
items = category.kbitem_set.all()
|
||||||
return render(request, template_name='helpdesk/kb_category.html',
|
return render(request, 'helpdesk/kb_category.html', {
|
||||||
context = {
|
|
||||||
'category': category,
|
'category': category,
|
||||||
'items': items,
|
'items': items,
|
||||||
'helpdesk_settings': helpdesk_settings,
|
'helpdesk_settings': helpdesk_settings,
|
||||||
@ -42,8 +36,7 @@ def category(request, slug):
|
|||||||
|
|
||||||
def item(request, item):
|
def item(request, item):
|
||||||
item = get_object_or_404(KBItem, pk=item)
|
item = get_object_or_404(KBItem, pk=item)
|
||||||
return render(request, template_name='helpdesk/kb_item.html',
|
return render(request, 'helpdesk/kb_item.html', {
|
||||||
context = {
|
|
||||||
'item': item,
|
'item': item,
|
||||||
'helpdesk_settings': helpdesk_settings,
|
'helpdesk_settings': helpdesk_settings,
|
||||||
})
|
})
|
||||||
@ -59,4 +52,3 @@ def vote(request, item):
|
|||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
return HttpResponseRedirect(item.get_absolute_url())
|
return HttpResponseRedirect(item.get_absolute_url())
|
||||||
|
|
||||||
|
@ -6,16 +6,15 @@ django-helpdesk - A Django powered ticket tracker for small enterprise.
|
|||||||
views/public.py - All public facing views, eg non-staff (no authentication
|
views/public.py - All public facing views, eg non-staff (no authentication
|
||||||
required) views.
|
required) views.
|
||||||
"""
|
"""
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.http import HttpResponseRedirect, Http404, HttpResponse
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render
|
||||||
from django.template import loader, Context, RequestContext
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from helpdesk import settings as helpdesk_settings
|
from helpdesk import settings as helpdesk_settings
|
||||||
from helpdesk.forms import PublicTicketForm
|
from helpdesk.forms import PublicTicketForm
|
||||||
from helpdesk.lib import send_templated_mail, text_is_spam
|
from helpdesk.lib import text_is_spam
|
||||||
from helpdesk.models import Ticket, Queue, UserSettings, KBCategory
|
from helpdesk.models import Ticket, Queue, UserSettings, KBCategory
|
||||||
|
|
||||||
|
|
||||||
@ -23,7 +22,9 @@ def homepage(request):
|
|||||||
if not request.user.is_authenticated() and helpdesk_settings.HELPDESK_REDIRECT_TO_LOGIN_BY_DEFAULT:
|
if not request.user.is_authenticated() and helpdesk_settings.HELPDESK_REDIRECT_TO_LOGIN_BY_DEFAULT:
|
||||||
return HttpResponseRedirect(reverse('helpdesk:login'))
|
return HttpResponseRedirect(reverse('helpdesk:login'))
|
||||||
|
|
||||||
if (request.user.is_staff or (request.user.is_authenticated() and helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE)):
|
if request.user.is_staff or \
|
||||||
|
(request.user.is_authenticated() and
|
||||||
|
helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE):
|
||||||
try:
|
try:
|
||||||
if request.user.usersettings.settings.get('login_view_ticketlist', False):
|
if request.user.usersettings.settings.get('login_view_ticketlist', False):
|
||||||
return HttpResponseRedirect(reverse('helpdesk:list'))
|
return HttpResponseRedirect(reverse('helpdesk:list'))
|
||||||
@ -34,7 +35,8 @@ def homepage(request):
|
|||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = PublicTicketForm(request.POST, request.FILES)
|
form = PublicTicketForm(request.POST, request.FILES)
|
||||||
form.fields['queue'].choices = [('', '--------')] + [[q.id, q.title] for q in Queue.objects.filter(allow_public_submission=True)]
|
form.fields['queue'].choices = [('', '--------')] + [
|
||||||
|
(q.id, q.title) for q in Queue.objects.filter(allow_public_submission=True)]
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
if text_is_spam(form.cleaned_data['body'], request):
|
if text_is_spam(form.cleaned_data['body'], request):
|
||||||
# This submission is spam. Let's not save it.
|
# This submission is spam. Let's not save it.
|
||||||
@ -59,12 +61,12 @@ def homepage(request):
|
|||||||
initial_data['submitter_email'] = request.user.email
|
initial_data['submitter_email'] = request.user.email
|
||||||
|
|
||||||
form = PublicTicketForm(initial=initial_data)
|
form = PublicTicketForm(initial=initial_data)
|
||||||
form.fields['queue'].choices = [('', '--------')] + [[q.id, q.title] for q in Queue.objects.filter(allow_public_submission=True)]
|
form.fields['queue'].choices = [('', '--------')] + [
|
||||||
|
(q.id, q.title) for q in Queue.objects.filter(allow_public_submission=True)]
|
||||||
|
|
||||||
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
|
||||||
@ -73,20 +75,22 @@ def homepage(request):
|
|||||||
|
|
||||||
def view_ticket(request):
|
def view_ticket(request):
|
||||||
ticket_req = request.GET.get('ticket', '')
|
ticket_req = request.GET.get('ticket', '')
|
||||||
ticket = False
|
|
||||||
email = request.GET.get('email', '')
|
email = request.GET.get('email', '')
|
||||||
error_message = ''
|
|
||||||
|
|
||||||
if ticket_req and email:
|
if ticket_req and email:
|
||||||
queue, ticket_id = Ticket.queue_and_id_from_query(ticket_req)
|
queue, ticket_id = Ticket.queue_and_id_from_query(ticket_req)
|
||||||
try:
|
try:
|
||||||
ticket = Ticket.objects.get(id=ticket_id, submitter_email__iexact=email)
|
ticket = Ticket.objects.get(id=ticket_id, submitter_email__iexact=email)
|
||||||
except:
|
except ObjectDoesNotExist:
|
||||||
ticket = False
|
|
||||||
error_message = _('Invalid ticket ID or e-mail address. Please try again.')
|
error_message = _('Invalid ticket ID or e-mail address. Please try again.')
|
||||||
|
|
||||||
if ticket:
|
return render(request, 'helpdesk/public_view_form.html', {
|
||||||
|
'ticket': False,
|
||||||
|
'email': email,
|
||||||
|
'error_message': error_message,
|
||||||
|
'helpdesk_settings': helpdesk_settings,
|
||||||
|
})
|
||||||
|
else:
|
||||||
if request.user.is_staff:
|
if request.user.is_staff:
|
||||||
redirect_url = reverse('helpdesk:view', args=[ticket_id])
|
redirect_url = reverse('helpdesk:view', args=[ticket_id])
|
||||||
if 'close' in request.GET:
|
if 'close' in request.GET:
|
||||||
@ -114,25 +118,16 @@ def view_ticket(request):
|
|||||||
if helpdesk_settings.HELPDESK_NAVIGATION_ENABLED:
|
if helpdesk_settings.HELPDESK_NAVIGATION_ENABLED:
|
||||||
redirect_url = reverse('helpdesk:view', args=[ticket_id])
|
redirect_url = reverse('helpdesk:view', args=[ticket_id])
|
||||||
|
|
||||||
return render(request, 'helpdesk/public_view_ticket.html',
|
return render(request, 'helpdesk/public_view_ticket.html', {
|
||||||
{
|
|
||||||
'ticket': ticket,
|
'ticket': ticket,
|
||||||
'helpdesk_settings': helpdesk_settings,
|
'helpdesk_settings': helpdesk_settings,
|
||||||
'next': redirect_url,
|
'next': redirect_url,
|
||||||
})
|
})
|
||||||
|
|
||||||
return render(request, template_name='helpdesk/public_view_form.html',
|
|
||||||
context = {
|
|
||||||
'ticket': ticket,
|
|
||||||
'email': email,
|
|
||||||
'error_message': error_message,
|
|
||||||
'helpdesk_settings': helpdesk_settings,
|
|
||||||
})
|
|
||||||
|
|
||||||
def change_language(request):
|
def change_language(request):
|
||||||
return_to = ''
|
return_to = ''
|
||||||
if 'return_to' in request.GET:
|
if 'return_to' in request.GET:
|
||||||
return_to = request.GET['return_to']
|
return_to = request.GET['return_to']
|
||||||
|
|
||||||
return render(request, template_name='helpdesk/public_change_language.html',
|
return render(request, 'helpdesk/public_change_language.html', {'next': return_to})
|
||||||
context = {'next': return_to})
|
|
||||||
|
@ -7,19 +7,12 @@ views/staff.py - The bulk of the application - provides most business logic and
|
|||||||
renders all staff-facing views.
|
renders all staff-facing views.
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import sys
|
|
||||||
|
|
||||||
from django import VERSION
|
from django import VERSION
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
try:
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
User = get_user_model()
|
from django.contrib.auth.decorators import user_passes_test
|
||||||
except ImportError:
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.contrib.auth.decorators import login_required, user_passes_test
|
|
||||||
from django.core.files.base import ContentFile
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.core.exceptions import ValidationError, PermissionDenied
|
from django.core.exceptions import ValidationError, PermissionDenied
|
||||||
from django.core import paginator
|
from django.core import paginator
|
||||||
@ -27,7 +20,6 @@ from django.db import connection
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.http import HttpResponseRedirect, Http404, HttpResponse
|
from django.http import HttpResponseRedirect, Http404, HttpResponse
|
||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
from django.template import loader, Context, RequestContext
|
|
||||||
from django.utils.dates import MONTHS_3
|
from django.utils.dates import MONTHS_3
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
@ -38,19 +30,33 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
from datetime import datetime as timezone
|
from datetime import datetime as timezone
|
||||||
|
|
||||||
from helpdesk.forms import TicketForm, UserSettingsForm, EmailIgnoreForm, EditTicketForm, TicketCCForm, EditFollowUpForm, TicketDependencyForm
|
from helpdesk.forms import (
|
||||||
from helpdesk.lib import send_templated_mail, query_to_dict, apply_query, safe_template_context
|
TicketForm, UserSettingsForm, EmailIgnoreForm, EditTicketForm, TicketCCForm,
|
||||||
from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment, SavedSearch, IgnoreEmail, TicketCC, TicketDependency
|
EditFollowUpForm, TicketDependencyForm
|
||||||
|
)
|
||||||
|
from helpdesk.lib import (
|
||||||
|
send_templated_mail, query_to_dict, apply_query, safe_template_context,
|
||||||
|
)
|
||||||
|
from helpdesk.models import (
|
||||||
|
Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment, SavedSearch,
|
||||||
|
IgnoreEmail, TicketCC, TicketDependency,
|
||||||
|
)
|
||||||
from helpdesk import settings as helpdesk_settings
|
from helpdesk import settings as helpdesk_settings
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
if helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE:
|
if helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE:
|
||||||
# treat 'normal' users like 'staff'
|
# treat 'normal' users like 'staff'
|
||||||
staff_member_required = user_passes_test(lambda u: u.is_authenticated() and u.is_active)
|
staff_member_required = user_passes_test(
|
||||||
|
lambda u: u.is_authenticated() and u.is_active)
|
||||||
else:
|
else:
|
||||||
staff_member_required = user_passes_test(lambda u: u.is_authenticated() and u.is_active and u.is_staff)
|
staff_member_required = user_passes_test(
|
||||||
|
lambda u: u.is_authenticated() and u.is_active and u.is_staff)
|
||||||
|
|
||||||
|
|
||||||
superuser_required = user_passes_test(lambda u: u.is_authenticated() and u.is_active and u.is_superuser)
|
superuser_required = user_passes_test(
|
||||||
|
lambda u: u.is_authenticated() and u.is_active and u.is_superuser)
|
||||||
|
|
||||||
|
|
||||||
def _get_user_queues(user):
|
def _get_user_queues(user):
|
||||||
@ -60,7 +66,9 @@ def _get_user_queues(user):
|
|||||||
:return: A Python list of Queues
|
:return: A Python list of Queues
|
||||||
"""
|
"""
|
||||||
all_queues = Queue.objects.all()
|
all_queues = Queue.objects.all()
|
||||||
limit_queues_by_user = helpdesk_settings.HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION and not user.is_superuser
|
limit_queues_by_user = \
|
||||||
|
helpdesk_settings.HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION \
|
||||||
|
and not user.is_superuser
|
||||||
if limit_queues_by_user:
|
if limit_queues_by_user:
|
||||||
id_list = [q.pk for q in all_queues if user.has_perm(q.permission_name)]
|
id_list = [q.pk for q in all_queues if user.has_perm(q.permission_name)]
|
||||||
return all_queues.filter(pk__in=id_list)
|
return all_queues.filter(pk__in=id_list)
|
||||||
@ -117,10 +125,10 @@ def dashboard(request):
|
|||||||
submitter_email=email_current_user,
|
submitter_email=email_current_user,
|
||||||
).order_by('status')
|
).order_by('status')
|
||||||
|
|
||||||
Tickets = 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)
|
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,
|
||||||
# to be displayed to the user. EG:
|
# to be displayed to the user. EG:
|
||||||
@ -153,8 +161,7 @@ def dashboard(request):
|
|||||||
|
|
||||||
dash_tickets = query_to_dict(cursor.fetchall(), cursor.description)
|
dash_tickets = query_to_dict(cursor.fetchall(), cursor.description)
|
||||||
|
|
||||||
return render(request, 'helpdesk/dashboard.html',
|
return render(request, 'helpdesk/dashboard.html', {
|
||||||
{
|
|
||||||
'user_tickets': tickets,
|
'user_tickets': tickets,
|
||||||
'user_tickets_closed_resolved': tickets_closed_resolved,
|
'user_tickets_closed_resolved': tickets_closed_resolved,
|
||||||
'unassigned_tickets': unassigned_tickets,
|
'unassigned_tickets': unassigned_tickets,
|
||||||
@ -171,8 +178,7 @@ def delete_ticket(request, ticket_id):
|
|||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
return render(request, template_name='helpdesk/delete_ticket.html',
|
return render(request, 'helpdesk/delete_ticket.html', {
|
||||||
context = {
|
|
||||||
'ticket': ticket,
|
'ticket': ticket,
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
@ -180,25 +186,26 @@ def delete_ticket(request, ticket_id):
|
|||||||
return HttpResponseRedirect(reverse('helpdesk:home'))
|
return HttpResponseRedirect(reverse('helpdesk:home'))
|
||||||
delete_ticket = staff_member_required(delete_ticket)
|
delete_ticket = staff_member_required(delete_ticket)
|
||||||
|
|
||||||
|
|
||||||
def followup_edit(request, ticket_id, followup_id):
|
def followup_edit(request, ticket_id, followup_id):
|
||||||
"Edit followup options with an ability to change the ticket."
|
"""Edit followup options with an ability to change the ticket."""
|
||||||
followup = get_object_or_404(FollowUp, id=followup_id)
|
followup = get_object_or_404(FollowUp, id=followup_id)
|
||||||
ticket = get_object_or_404(Ticket, id=ticket_id)
|
ticket = get_object_or_404(Ticket, id=ticket_id)
|
||||||
if not _has_access_to_queue(request.user, ticket.queue):
|
if not _has_access_to_queue(request.user, ticket.queue):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
form = EditFollowUpForm(initial=
|
form = EditFollowUpForm(initial={
|
||||||
{'title': escape(followup.title),
|
'title': escape(followup.title),
|
||||||
'ticket': followup.ticket,
|
'ticket': followup.ticket,
|
||||||
'comment': escape(followup.comment),
|
'comment': escape(followup.comment),
|
||||||
'public': followup.public,
|
'public': followup.public,
|
||||||
'new_status': followup.new_status,
|
'new_status': followup.new_status,
|
||||||
})
|
})
|
||||||
|
|
||||||
ticketcc_string, SHOW_SUBSCRIBE = return_ticketccstring_and_show_subscribe(request.user, ticket)
|
ticketcc_string, show_subscribe = \
|
||||||
|
return_ticketccstring_and_show_subscribe(request.user, ticket)
|
||||||
|
|
||||||
return render(request, template_name='helpdesk/followup_edit.html',
|
return render(request, 'helpdesk/followup_edit.html', {
|
||||||
context = {
|
|
||||||
'followup': followup,
|
'followup': followup,
|
||||||
'ticket': ticket,
|
'ticket': ticket,
|
||||||
'form': form,
|
'form': form,
|
||||||
@ -229,8 +236,9 @@ def followup_edit(request, ticket_id, followup_id):
|
|||||||
return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket.id]))
|
return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket.id]))
|
||||||
followup_edit = staff_member_required(followup_edit)
|
followup_edit = staff_member_required(followup_edit)
|
||||||
|
|
||||||
|
|
||||||
def followup_delete(request, ticket_id, followup_id):
|
def followup_delete(request, ticket_id, followup_id):
|
||||||
''' followup delete for superuser'''
|
"""followup delete for superuser"""
|
||||||
|
|
||||||
ticket = get_object_or_404(Ticket, id=ticket_id)
|
ticket = get_object_or_404(Ticket, id=ticket_id)
|
||||||
if not request.user.is_superuser:
|
if not request.user.is_superuser:
|
||||||
@ -262,8 +270,9 @@ def view_ticket(request, ticket_id):
|
|||||||
|
|
||||||
if 'subscribe' in request.GET:
|
if 'subscribe' in request.GET:
|
||||||
# Allow the user to subscribe him/herself to the ticket whilst viewing it.
|
# Allow the user to subscribe him/herself to the ticket whilst viewing it.
|
||||||
ticketcc_string, SHOW_SUBSCRIBE = return_ticketccstring_and_show_subscribe(request.user, ticket)
|
ticket_cc, show_subscribe = \
|
||||||
if SHOW_SUBSCRIBE:
|
return_ticketccstring_and_show_subscribe(request.user, ticket)
|
||||||
|
if show_subscribe:
|
||||||
subscribe_staff_member_to_ticket(ticket, request.user)
|
subscribe_staff_member_to_ticket(ticket, request.user)
|
||||||
return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket.id]))
|
return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket.id]))
|
||||||
|
|
||||||
@ -290,26 +299,27 @@ 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})
|
||||||
|
|
||||||
ticketcc_string, SHOW_SUBSCRIBE = return_ticketccstring_and_show_subscribe(request.user, ticket)
|
ticketcc_string, show_subscribe = \
|
||||||
|
return_ticketccstring_and_show_subscribe(request.user, ticket)
|
||||||
|
|
||||||
return render(request, template_name='helpdesk/ticket.html',
|
return render(request, 'helpdesk/ticket.html', {
|
||||||
context = {
|
|
||||||
'ticket': ticket,
|
'ticket': ticket,
|
||||||
'form': form,
|
'form': form,
|
||||||
'active_users': users,
|
'active_users': users,
|
||||||
'priorities': Ticket.PRIORITY_CHOICES,
|
'priorities': Ticket.PRIORITY_CHOICES,
|
||||||
'preset_replies': PreSetReply.objects.filter(Q(queues=ticket.queue) | Q(queues__isnull=True)),
|
'preset_replies': PreSetReply.objects.filter(
|
||||||
|
Q(queues=ticket.queue) | Q(queues__isnull=True)),
|
||||||
'ticketcc_string': ticketcc_string,
|
'ticketcc_string': ticketcc_string,
|
||||||
'SHOW_SUBSCRIBE': SHOW_SUBSCRIBE,
|
'SHOW_SUBSCRIBE': show_subscribe,
|
||||||
})
|
})
|
||||||
view_ticket = staff_member_required(view_ticket)
|
view_ticket = staff_member_required(view_ticket)
|
||||||
|
|
||||||
|
|
||||||
def return_ticketccstring_and_show_subscribe(user, ticket):
|
def return_ticketccstring_and_show_subscribe(user, ticket):
|
||||||
''' used in view_ticket() and followup_edit()'''
|
"""used in view_ticket() and followup_edit()"""
|
||||||
# create the ticketcc_string and check whether current user is already
|
# create the ticketcc_string and check whether current user is already
|
||||||
# subscribed
|
# subscribed
|
||||||
username = user.get_username().upper()
|
username = user.get_username().upper()
|
||||||
@ -321,14 +331,14 @@ def return_ticketccstring_and_show_subscribe(user, ticket):
|
|||||||
ticketcc_string = ''
|
ticketcc_string = ''
|
||||||
all_ticketcc = ticket.ticketcc_set.all()
|
all_ticketcc = ticket.ticketcc_set.all()
|
||||||
counter_all_ticketcc = len(all_ticketcc) - 1
|
counter_all_ticketcc = len(all_ticketcc) - 1
|
||||||
SHOW_SUBSCRIBE = True
|
show_subscribe = True
|
||||||
for i, ticketcc in enumerate(all_ticketcc):
|
for i, ticketcc in enumerate(all_ticketcc):
|
||||||
ticketcc_this_entry = str(ticketcc.display)
|
ticketcc_this_entry = str(ticketcc.display)
|
||||||
ticketcc_string = ticketcc_string + ticketcc_this_entry
|
ticketcc_string += ticketcc_this_entry
|
||||||
if i < counter_all_ticketcc:
|
if i < counter_all_ticketcc:
|
||||||
ticketcc_string = ticketcc_string + ', '
|
ticketcc_string += ', '
|
||||||
if strings_to_check.__contains__(ticketcc_this_entry.upper()):
|
if strings_to_check.__contains__(ticketcc_this_entry.upper()):
|
||||||
SHOW_SUBSCRIBE = False
|
show_subscribe = False
|
||||||
|
|
||||||
# check whether current user is a submitter or assigned to ticket
|
# check whether current user is a submitter or assigned to ticket
|
||||||
assignedto_username = str(ticket.assigned_to).upper()
|
assignedto_username = str(ticket.assigned_to).upper()
|
||||||
@ -337,24 +347,30 @@ def return_ticketccstring_and_show_subscribe(user, ticket):
|
|||||||
strings_to_check.append(assignedto_username)
|
strings_to_check.append(assignedto_username)
|
||||||
strings_to_check.append(submitter_email)
|
strings_to_check.append(submitter_email)
|
||||||
if strings_to_check.__contains__(username) or strings_to_check.__contains__(useremail):
|
if strings_to_check.__contains__(username) or strings_to_check.__contains__(useremail):
|
||||||
SHOW_SUBSCRIBE = False
|
show_subscribe = False
|
||||||
|
|
||||||
return ticketcc_string, SHOW_SUBSCRIBE
|
return ticketcc_string, show_subscribe
|
||||||
|
|
||||||
|
|
||||||
def subscribe_staff_member_to_ticket(ticket, user):
|
def subscribe_staff_member_to_ticket(ticket, user):
|
||||||
''' used in view_ticket() and update_ticket() '''
|
"""used in view_ticket() and update_ticket()"""
|
||||||
ticketcc = TicketCC()
|
ticketcc = TicketCC(
|
||||||
ticketcc.ticket = ticket
|
ticket=ticket,
|
||||||
ticketcc.user = user
|
user=user,
|
||||||
ticketcc.can_view = True
|
can_view=True,
|
||||||
ticketcc.can_update = True
|
can_update=True,
|
||||||
|
)
|
||||||
ticketcc.save()
|
ticketcc.save()
|
||||||
|
|
||||||
|
|
||||||
def update_ticket(request, ticket_id, public=False):
|
def update_ticket(request, ticket_id, public=False):
|
||||||
if not (public or (request.user.is_authenticated() and request.user.is_active and (request.user.is_staff or helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE))):
|
if not (public or (
|
||||||
return HttpResponseRedirect('%s?next=%s' % (reverse('helpdesk:login'), request.path))
|
request.user.is_authenticated() and
|
||||||
|
request.user.is_active and (
|
||||||
|
request.user.is_staff or
|
||||||
|
helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE))):
|
||||||
|
return HttpResponseRedirect('%s?next=%s' %
|
||||||
|
(reverse('helpdesk:login'), request.path))
|
||||||
|
|
||||||
ticket = get_object_or_404(Ticket, id=ticket_id)
|
ticket = get_object_or_404(Ticket, id=ticket_id)
|
||||||
|
|
||||||
@ -384,7 +400,8 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
title == ticket.title,
|
title == ticket.title,
|
||||||
priority == int(ticket.priority),
|
priority == int(ticket.priority),
|
||||||
due_date == ticket.due_date,
|
due_date == ticket.due_date,
|
||||||
(owner == -1) or (not owner and not ticket.assigned_to) or (owner and User.objects.get(id=owner) == ticket.assigned_to),
|
(owner == -1) or (not owner and not ticket.assigned_to) or
|
||||||
|
(owner and User.objects.get(id=owner) == ticket.assigned_to),
|
||||||
])
|
])
|
||||||
if no_changes:
|
if no_changes:
|
||||||
return return_to_ticket(request.user, helpdesk_settings, ticket)
|
return return_to_ticket(request.user, helpdesk_settings, ticket)
|
||||||
@ -398,7 +415,8 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
# then the following line will give us a crash, since django expects {% if %}
|
# then the following line will give us a crash, since django expects {% if %}
|
||||||
# to be closed with an {% endif %} tag.
|
# to be closed with an {% endif %} tag.
|
||||||
|
|
||||||
# get_template_from_string was removed in Django 1.8 http://django.readthedocs.org/en/1.8.x/ref/templates/upgrading.html
|
# get_template_from_string was removed in Django 1.8
|
||||||
|
# http://django.readthedocs.org/en/1.8.x/ref/templates/upgrading.html
|
||||||
try:
|
try:
|
||||||
from django.template import engines
|
from django.template import engines
|
||||||
template_func = engines['django'].from_string
|
template_func = engines['django'].from_string
|
||||||
@ -455,7 +473,7 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
|
|
||||||
files = []
|
files = []
|
||||||
if request.FILES:
|
if request.FILES:
|
||||||
import mimetypes, os
|
import mimetypes
|
||||||
for file in request.FILES.getlist('attachment'):
|
for file in request.FILES.getlist('attachment'):
|
||||||
filename = file.name.encode('ascii', 'ignore')
|
filename = file.name.encode('ascii', 'ignore')
|
||||||
a = Attachment(
|
a = Attachment(
|
||||||
@ -472,7 +490,6 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
# settings.MAX_EMAIL_ATTACHMENT_SIZE) are sent via email.
|
# settings.MAX_EMAIL_ATTACHMENT_SIZE) are sent via email.
|
||||||
files.append([a.filename, a.file])
|
files.append([a.filename, a.file])
|
||||||
|
|
||||||
|
|
||||||
if title != ticket.title:
|
if title != ticket.title:
|
||||||
c = TicketChange(
|
c = TicketChange(
|
||||||
followup=f,
|
followup=f,
|
||||||
@ -503,7 +520,7 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
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
|
||||||
|
|
||||||
@ -517,9 +534,9 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
comment=f.comment,
|
comment=f.comment,
|
||||||
)
|
)
|
||||||
|
|
||||||
if public and (f.comment or (f.new_status in (Ticket.RESOLVED_STATUS, Ticket.CLOSED_STATUS))):
|
if public and (f.comment or (
|
||||||
|
f.new_status in (Ticket.RESOLVED_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:
|
||||||
@ -554,7 +571,10 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
)
|
)
|
||||||
messages_sent_to.append(cc.email_address)
|
messages_sent_to.append(cc.email_address)
|
||||||
|
|
||||||
if ticket.assigned_to and request.user != ticket.assigned_to and ticket.assigned_to.email and ticket.assigned_to.email not in messages_sent_to:
|
if ticket.assigned_to and \
|
||||||
|
request.user != ticket.assigned_to and \
|
||||||
|
ticket.assigned_to.email and \
|
||||||
|
ticket.assigned_to.email not in messages_sent_to:
|
||||||
# We only send e-mails to staff members if the ticket is updated by
|
# We only send e-mails to staff members if the ticket is updated by
|
||||||
# another user. The actual template varies, depending on what has been
|
# another user. The actual template varies, depending on what has been
|
||||||
# changed.
|
# changed.
|
||||||
@ -567,7 +587,13 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
else:
|
else:
|
||||||
template_staff = 'updated_owner'
|
template_staff = 'updated_owner'
|
||||||
|
|
||||||
if (not reassigned or ( reassigned and ticket.assigned_to.usersettings.settings.get('email_on_ticket_assign', False))) or (not reassigned and ticket.assigned_to.usersettings.settings.get('email_on_ticket_change', False)):
|
if (not reassigned or
|
||||||
|
(reassigned and
|
||||||
|
ticket.assigned_to.usersettings.settings.get(
|
||||||
|
'email_on_ticket_assign', False))) or \
|
||||||
|
(not reassigned and
|
||||||
|
ticket.assigned_to.usersettings.settings.get(
|
||||||
|
'email_on_ticket_change', False)):
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
template_staff,
|
template_staff,
|
||||||
context,
|
context,
|
||||||
@ -609,7 +635,7 @@ def update_ticket(request, ticket_id, public=False):
|
|||||||
|
|
||||||
|
|
||||||
def return_to_ticket(user, helpdesk_settings, ticket):
|
def return_to_ticket(user, helpdesk_settings, ticket):
|
||||||
''' Helpder function for update_ticket '''
|
"""Helper function for update_ticket"""
|
||||||
|
|
||||||
if user.is_staff or helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE:
|
if user.is_staff or helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE:
|
||||||
return HttpResponseRedirect(ticket.get_absolute_url())
|
return HttpResponseRedirect(ticket.get_absolute_url())
|
||||||
@ -638,29 +664,47 @@ def mass_update(request):
|
|||||||
if action == 'assign' and t.assigned_to != user:
|
if action == 'assign' and t.assigned_to != user:
|
||||||
t.assigned_to = user
|
t.assigned_to = user
|
||||||
t.save()
|
t.save()
|
||||||
f = FollowUp(ticket=t, date=timezone.now(), title=_('Assigned to %(username)s in bulk update' % {'username': user.get_username()}), public=True, user=request.user)
|
f = FollowUp(ticket=t,
|
||||||
|
date=timezone.now(),
|
||||||
|
title=_('Assigned to %(username)s in bulk update' % {
|
||||||
|
'username': user.get_username()
|
||||||
|
}),
|
||||||
|
public=True,
|
||||||
|
user=request.user)
|
||||||
f.save()
|
f.save()
|
||||||
elif action == 'unassign' and t.assigned_to is not None:
|
elif action == 'unassign' and t.assigned_to is not None:
|
||||||
t.assigned_to = None
|
t.assigned_to = None
|
||||||
t.save()
|
t.save()
|
||||||
f = FollowUp(ticket=t, date=timezone.now(), title=_('Unassigned in bulk update'), public=True, user=request.user)
|
f = FollowUp(ticket=t,
|
||||||
|
date=timezone.now(),
|
||||||
|
title=_('Unassigned in bulk update'),
|
||||||
|
public=True,
|
||||||
|
user=request.user)
|
||||||
f.save()
|
f.save()
|
||||||
elif action == 'close' and t.status != Ticket.CLOSED_STATUS:
|
elif action == 'close' and t.status != Ticket.CLOSED_STATUS:
|
||||||
t.status = Ticket.CLOSED_STATUS
|
t.status = Ticket.CLOSED_STATUS
|
||||||
t.save()
|
t.save()
|
||||||
f = FollowUp(ticket=t, date=timezone.now(), title=_('Closed in bulk update'), public=False, user=request.user, new_status=Ticket.CLOSED_STATUS)
|
f = FollowUp(ticket=t,
|
||||||
|
date=timezone.now(),
|
||||||
|
title=_('Closed in bulk update'),
|
||||||
|
public=False,
|
||||||
|
user=request.user,
|
||||||
|
new_status=Ticket.CLOSED_STATUS)
|
||||||
f.save()
|
f.save()
|
||||||
elif action == 'close_public' and t.status != Ticket.CLOSED_STATUS:
|
elif action == 'close_public' and t.status != Ticket.CLOSED_STATUS:
|
||||||
t.status = Ticket.CLOSED_STATUS
|
t.status = Ticket.CLOSED_STATUS
|
||||||
t.save()
|
t.save()
|
||||||
f = FollowUp(ticket=t, date=timezone.now(), title=_('Closed in bulk update'), public=True, user=request.user, new_status=Ticket.CLOSED_STATUS)
|
f = FollowUp(ticket=t,
|
||||||
|
date=timezone.now(),
|
||||||
|
title=_('Closed in bulk update'),
|
||||||
|
public=True,
|
||||||
|
user=request.user,
|
||||||
|
new_status=Ticket.CLOSED_STATUS)
|
||||||
f.save()
|
f.save()
|
||||||
# Send email to Submitter, Owner, Queue CC
|
# Send email to Submitter, Owner, Queue CC
|
||||||
context = safe_template_context(t)
|
context = safe_template_context(t)
|
||||||
context.update(
|
context.update(resolution=t.resolution,
|
||||||
resolution = t.resolution,
|
queue=t.queue)
|
||||||
queue = t.queue,
|
|
||||||
)
|
|
||||||
|
|
||||||
messages_sent_to = []
|
messages_sent_to = []
|
||||||
|
|
||||||
@ -685,7 +729,10 @@ def mass_update(request):
|
|||||||
)
|
)
|
||||||
messages_sent_to.append(cc.email_address)
|
messages_sent_to.append(cc.email_address)
|
||||||
|
|
||||||
if t.assigned_to and request.user != t.assigned_to and t.assigned_to.email and t.assigned_to.email not in messages_sent_to:
|
if t.assigned_to and \
|
||||||
|
request.user != t.assigned_to and \
|
||||||
|
t.assigned_to.email and \
|
||||||
|
t.assigned_to.email not in messages_sent_to:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
'closed_owner',
|
'closed_owner',
|
||||||
context,
|
context,
|
||||||
@ -695,7 +742,8 @@ def mass_update(request):
|
|||||||
)
|
)
|
||||||
messages_sent_to.append(t.assigned_to.email)
|
messages_sent_to.append(t.assigned_to.email)
|
||||||
|
|
||||||
if t.queue.updated_ticket_cc and t.queue.updated_ticket_cc not in messages_sent_to:
|
if t.queue.updated_ticket_cc and \
|
||||||
|
t.queue.updated_ticket_cc not in messages_sent_to:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
'closed_cc',
|
'closed_cc',
|
||||||
context,
|
context,
|
||||||
@ -710,6 +758,7 @@ def mass_update(request):
|
|||||||
return HttpResponseRedirect(reverse('helpdesk:list'))
|
return HttpResponseRedirect(reverse('helpdesk:list'))
|
||||||
mass_update = staff_member_required(mass_update)
|
mass_update = staff_member_required(mass_update)
|
||||||
|
|
||||||
|
|
||||||
def ticket_list(request):
|
def ticket_list(request):
|
||||||
context = {}
|
context = {}
|
||||||
|
|
||||||
@ -781,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
|
||||||
@ -830,14 +878,14 @@ def ticket_list(request):
|
|||||||
if date_to:
|
if date_to:
|
||||||
query_params['filtering']['created__lte'] = date_to
|
query_params['filtering']['created__lte'] = date_to
|
||||||
|
|
||||||
### KEYWORD SEARCHING
|
# KEYWORD SEARCHING
|
||||||
q = request.GET.get('q', None)
|
q = request.GET.get('q', None)
|
||||||
|
|
||||||
if q:
|
if q:
|
||||||
context = dict(context, query=q)
|
context = dict(context, query=q)
|
||||||
query_params['search_string'] = q
|
query_params['search_string'] = q
|
||||||
|
|
||||||
### SORTING
|
# SORTING
|
||||||
sort = request.GET.get('sort', None)
|
sort = request.GET.get('sort', None)
|
||||||
if sort not in ('status', 'assigned_to', 'created', 'title', 'queue', 'priority'):
|
if sort not in ('status', 'assigned_to', 'created', 'title', 'queue', 'priority'):
|
||||||
sort = 'created'
|
sort = 'created'
|
||||||
@ -858,7 +906,9 @@ def ticket_list(request):
|
|||||||
}
|
}
|
||||||
ticket_qs = apply_query(tickets, query_params)
|
ticket_qs = apply_query(tickets, query_params)
|
||||||
|
|
||||||
ticket_paginator = paginator.Paginator(ticket_qs, request.user.usersettings.settings.get('tickets_per_page') or 20)
|
ticket_paginator = paginator.Paginator(
|
||||||
|
ticket_qs,
|
||||||
|
request.user.usersettings.settings.get('tickets_per_page') or 20)
|
||||||
try:
|
try:
|
||||||
page = int(request.GET.get('page', '1'))
|
page = int(request.GET.get('page', '1'))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -871,8 +921,13 @@ def ticket_list(request):
|
|||||||
|
|
||||||
search_message = ''
|
search_message = ''
|
||||||
if 'query' in context and settings.DATABASES['default']['ENGINE'].endswith('sqlite'):
|
if 'query' in context and settings.DATABASES['default']['ENGINE'].endswith('sqlite'):
|
||||||
search_message = _('<p><strong>Note:</strong> Your keyword search is case sensitive because of your database. This means the search will <strong>not</strong> be accurate. By switching to a different database system you will gain better searching! For more information, read the <a href="http://docs.djangoproject.com/en/dev/ref/databases/#sqlite-string-matching">Django Documentation on string matching in SQLite</a>.')
|
search_message = _(
|
||||||
|
'<p><strong>Note:</strong> Your keyword search is case sensitive '
|
||||||
|
'because of your database. This means the search will <strong>not</strong> '
|
||||||
|
'be accurate. By switching to a different database system you will gain '
|
||||||
|
'better searching! For more information, read the '
|
||||||
|
'<a href="http://docs.djangoproject.com/en/dev/ref/databases/#sqlite-string-matching">'
|
||||||
|
'Django Documentation on string matching in SQLite</a>.')
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from helpdesk.lib import b64encode
|
from helpdesk.lib import b64encode
|
||||||
@ -883,9 +938,7 @@ def ticket_list(request):
|
|||||||
querydict = request.GET.copy()
|
querydict = request.GET.copy()
|
||||||
querydict.pop('page', 1)
|
querydict.pop('page', 1)
|
||||||
|
|
||||||
|
return render(request, 'helpdesk/ticket_list.html', dict(
|
||||||
return render(request, 'helpdesk/ticket_list.html',
|
|
||||||
dict(
|
|
||||||
context,
|
context,
|
||||||
query_string=querydict.urlencode(),
|
query_string=querydict.urlencode(),
|
||||||
tickets=tickets,
|
tickets=tickets,
|
||||||
@ -915,12 +968,10 @@ def edit_ticket(request, ticket_id):
|
|||||||
else:
|
else:
|
||||||
form = EditTicketForm(instance=ticket)
|
form = EditTicketForm(instance=ticket)
|
||||||
|
|
||||||
return render(request, template_name='helpdesk/edit_ticket.html',
|
return render(request, 'helpdesk/edit_ticket.html', {'form': form})
|
||||||
context = {
|
|
||||||
'form': form,
|
|
||||||
})
|
|
||||||
edit_ticket = staff_member_required(edit_ticket)
|
edit_ticket = staff_member_required(edit_ticket)
|
||||||
|
|
||||||
|
|
||||||
def create_ticket(request):
|
def create_ticket(request):
|
||||||
if helpdesk_settings.HELPDESK_STAFF_ONLY_TICKET_OWNERS:
|
if helpdesk_settings.HELPDESK_STAFF_ONLY_TICKET_OWNERS:
|
||||||
assignable_users = User.objects.filter(is_active=True, is_staff=True).order_by(User.USERNAME_FIELD)
|
assignable_users = User.objects.filter(is_active=True, is_staff=True).order_by(User.USERNAME_FIELD)
|
||||||
@ -929,8 +980,10 @@ def create_ticket(request):
|
|||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = TicketForm(request.POST, request.FILES)
|
form = TicketForm(request.POST, request.FILES)
|
||||||
form.fields['queue'].choices = [('', '--------')] + [[q.id, q.title] for q in Queue.objects.all()]
|
form.fields['queue'].choices = [('', '--------')] + [
|
||||||
form.fields['assigned_to'].choices = [('', '--------')] + [[u.id, u.get_username()] for u in assignable_users]
|
(q.id, q.title) for q in Queue.objects.all()]
|
||||||
|
form.fields['assigned_to'].choices = [('', '--------')] + [
|
||||||
|
(u.id, u.get_username()) for u in assignable_users]
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
ticket = form.save(user=request.user)
|
ticket = form.save(user=request.user)
|
||||||
if _has_access_to_queue(request.user, ticket.queue):
|
if _has_access_to_queue(request.user, ticket.queue):
|
||||||
@ -945,13 +998,14 @@ def create_ticket(request):
|
|||||||
initial_data['queue'] = request.GET['queue']
|
initial_data['queue'] = request.GET['queue']
|
||||||
|
|
||||||
form = TicketForm(initial=initial_data)
|
form = TicketForm(initial=initial_data)
|
||||||
form.fields['queue'].choices = [('', '--------')] + [[q.id, q.title] for q in Queue.objects.all()]
|
form.fields['queue'].choices = [('', '--------')] + [
|
||||||
form.fields['assigned_to'].choices = [('', '--------')] + [[u.id, u.get_username()] for u in assignable_users]
|
(q.id, q.title) for q in Queue.objects.all()]
|
||||||
|
form.fields['assigned_to'].choices = [('', '--------')] + [
|
||||||
|
(u.id, u.get_username()) for u in assignable_users]
|
||||||
if helpdesk_settings.HELPDESK_CREATE_TICKET_HIDE_ASSIGNED_TO:
|
if helpdesk_settings.HELPDESK_CREATE_TICKET_HIDE_ASSIGNED_TO:
|
||||||
form.fields['assigned_to'].widget = forms.HiddenInput()
|
form.fields['assigned_to'].widget = forms.HiddenInput()
|
||||||
|
|
||||||
return render(request, template_name='helpdesk/create_ticket.html',
|
return render(request, 'helpdesk/create_ticket.html', {'form': form})
|
||||||
context = {'form': form})
|
|
||||||
create_ticket = staff_member_required(create_ticket)
|
create_ticket = staff_member_required(create_ticket)
|
||||||
|
|
||||||
|
|
||||||
@ -960,7 +1014,7 @@ def raw_details(request, type):
|
|||||||
# in the future it needs to be expanded to include other items. All it
|
# in the future it needs to be expanded to include other items. All it
|
||||||
# does is return a plain-text representation of an object.
|
# does is return a plain-text representation of an object.
|
||||||
|
|
||||||
if not type in ('preset',):
|
if type not in ('preset',):
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
if type == 'preset' and request.GET.get('id', False):
|
if type == 'preset' and request.GET.get('id', False):
|
||||||
@ -1007,18 +1061,14 @@ unhold_ticket = staff_member_required(unhold_ticket)
|
|||||||
|
|
||||||
|
|
||||||
def rss_list(request):
|
def rss_list(request):
|
||||||
return render(request, template_name='helpdesk/rss_list.html',
|
return render(request, 'helpdesk/rss_list.html', {'queues': Queue.objects.all()})
|
||||||
context = {
|
|
||||||
'queues': Queue.objects.all(),
|
|
||||||
})
|
|
||||||
rss_list = staff_member_required(rss_list)
|
rss_list = staff_member_required(rss_list)
|
||||||
|
|
||||||
|
|
||||||
def report_index(request):
|
def report_index(request):
|
||||||
number_tickets = Ticket.objects.all().count()
|
number_tickets = Ticket.objects.all().count()
|
||||||
saved_query = request.GET.get('saved_query', None)
|
saved_query = request.GET.get('saved_query', None)
|
||||||
return render(request, template_name='helpdesk/report_index.html',
|
return render(request, 'helpdesk/report_index.html', {
|
||||||
context = {
|
|
||||||
'number_tickets': number_tickets,
|
'number_tickets': number_tickets,
|
||||||
'saved_query': saved_query,
|
'saved_query': saved_query,
|
||||||
})
|
})
|
||||||
@ -1026,7 +1076,9 @@ report_index = staff_member_required(report_index)
|
|||||||
|
|
||||||
|
|
||||||
def run_report(request, report):
|
def run_report(request, report):
|
||||||
if Ticket.objects.all().count() == 0 or report not in ('queuemonth', 'usermonth', 'queuestatus', 'queuepriority', 'userstatus', 'userpriority', 'userqueue', 'daysuntilticketclosedbymonth'):
|
if Ticket.objects.all().count() == 0 or report not in (
|
||||||
|
'queuemonth', 'usermonth', 'queuestatus', 'queuepriority', 'userstatus',
|
||||||
|
'userpriority', 'userqueue', 'daysuntilticketclosedbymonth'):
|
||||||
return HttpResponseRedirect(reverse("helpdesk_report_index"))
|
return HttpResponseRedirect(reverse("helpdesk_report_index"))
|
||||||
|
|
||||||
report_queryset = Ticket.objects.all().select_related().filter(
|
report_queryset = Ticket.objects.all().select_related().filter(
|
||||||
@ -1059,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
|
||||||
@ -1191,8 +1244,7 @@ def run_report(request, report):
|
|||||||
data.append(summarytable[item, hdr])
|
data.append(summarytable[item, hdr])
|
||||||
table.append([item] + data)
|
table.append([item] + data)
|
||||||
|
|
||||||
return render(request, 'helpdesk/report_output.html',
|
return render(request, 'helpdesk/report_output.html', {
|
||||||
{
|
|
||||||
'title': title,
|
'title': title,
|
||||||
'charttype': charttype,
|
'charttype': charttype,
|
||||||
'data': table,
|
'data': table,
|
||||||
@ -1227,10 +1279,7 @@ def delete_saved_query(request, id):
|
|||||||
query.delete()
|
query.delete()
|
||||||
return HttpResponseRedirect(reverse('helpdesk:list'))
|
return HttpResponseRedirect(reverse('helpdesk:list'))
|
||||||
else:
|
else:
|
||||||
return render(request, template_name='helpdesk/confirm_delete_saved_query.html',
|
return render(request, 'helpdesk/confirm_delete_saved_query.html', {'query': query})
|
||||||
context = {
|
|
||||||
'query': query,
|
|
||||||
})
|
|
||||||
delete_saved_query = staff_member_required(delete_saved_query)
|
delete_saved_query = staff_member_required(delete_saved_query)
|
||||||
|
|
||||||
|
|
||||||
@ -1244,16 +1293,12 @@ def user_settings(request):
|
|||||||
else:
|
else:
|
||||||
form = UserSettingsForm(s.settings)
|
form = UserSettingsForm(s.settings)
|
||||||
|
|
||||||
return render(request, template_name='helpdesk/user_settings.html',
|
return render(request, 'helpdesk/user_settings.html', {'form': form})
|
||||||
context = {
|
|
||||||
'form': form,
|
|
||||||
})
|
|
||||||
user_settings = staff_member_required(user_settings)
|
user_settings = staff_member_required(user_settings)
|
||||||
|
|
||||||
|
|
||||||
def email_ignore(request):
|
def email_ignore(request):
|
||||||
return render(request, template_name='helpdesk/email_ignore_list.html',
|
return render(request, 'helpdesk/email_ignore_list.html', {
|
||||||
context = {
|
|
||||||
'ignore_list': IgnoreEmail.objects.all(),
|
'ignore_list': IgnoreEmail.objects.all(),
|
||||||
})
|
})
|
||||||
email_ignore = superuser_required(email_ignore)
|
email_ignore = superuser_required(email_ignore)
|
||||||
@ -1263,15 +1308,12 @@ def email_ignore_add(request):
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = EmailIgnoreForm(request.POST)
|
form = EmailIgnoreForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
ignore = form.save()
|
form.save()
|
||||||
return HttpResponseRedirect(reverse('helpdesk:email_ignore'))
|
return HttpResponseRedirect(reverse('helpdesk:email_ignore'))
|
||||||
else:
|
else:
|
||||||
form = EmailIgnoreForm(request.GET)
|
form = EmailIgnoreForm(request.GET)
|
||||||
|
|
||||||
return render(request, template_name='helpdesk/email_ignore_add.html',
|
return render(request, 'helpdesk/email_ignore_add.html', {'form': form})
|
||||||
context = {
|
|
||||||
'form': form,
|
|
||||||
})
|
|
||||||
email_ignore_add = superuser_required(email_ignore_add)
|
email_ignore_add = superuser_required(email_ignore_add)
|
||||||
|
|
||||||
|
|
||||||
@ -1281,25 +1323,23 @@ def email_ignore_del(request, id):
|
|||||||
ignore.delete()
|
ignore.delete()
|
||||||
return HttpResponseRedirect(reverse('helpdesk:email_ignore'))
|
return HttpResponseRedirect(reverse('helpdesk:email_ignore'))
|
||||||
else:
|
else:
|
||||||
return render(request, template_name='helpdesk/email_ignore_del.html',
|
return render(request, 'helpdesk/email_ignore_del.html', {'ignore': ignore})
|
||||||
context = {
|
|
||||||
'ignore': ignore,
|
|
||||||
})
|
|
||||||
email_ignore_del = superuser_required(email_ignore_del)
|
email_ignore_del = superuser_required(email_ignore_del)
|
||||||
|
|
||||||
|
|
||||||
def ticket_cc(request, ticket_id):
|
def ticket_cc(request, ticket_id):
|
||||||
ticket = get_object_or_404(Ticket, id=ticket_id)
|
ticket = get_object_or_404(Ticket, id=ticket_id)
|
||||||
if not _has_access_to_queue(request.user, ticket.queue):
|
if not _has_access_to_queue(request.user, ticket.queue):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
||||||
copies_to = ticket.ticketcc_set.all()
|
copies_to = ticket.ticketcc_set.all()
|
||||||
return render(request, template_name='helpdesk/ticket_cc_list.html',
|
return render(request, 'helpdesk/ticket_cc_list.html', {
|
||||||
context = {
|
|
||||||
'copies_to': copies_to,
|
'copies_to': copies_to,
|
||||||
'ticket': ticket,
|
'ticket': ticket,
|
||||||
})
|
})
|
||||||
ticket_cc = staff_member_required(ticket_cc)
|
ticket_cc = staff_member_required(ticket_cc)
|
||||||
|
|
||||||
|
|
||||||
def ticket_cc_add(request, ticket_id):
|
def ticket_cc_add(request, ticket_id):
|
||||||
ticket = get_object_or_404(Ticket, id=ticket_id)
|
ticket = get_object_or_404(Ticket, id=ticket_id)
|
||||||
if not _has_access_to_queue(request.user, ticket.queue):
|
if not _has_access_to_queue(request.user, ticket.queue):
|
||||||
@ -1311,28 +1351,28 @@ def ticket_cc_add(request, ticket_id):
|
|||||||
ticketcc = form.save(commit=False)
|
ticketcc = form.save(commit=False)
|
||||||
ticketcc.ticket = ticket
|
ticketcc.ticket = ticket
|
||||||
ticketcc.save()
|
ticketcc.save()
|
||||||
return HttpResponseRedirect(reverse('helpdesk:ticket_cc', kwargs={'ticket_id': ticket.id}))
|
return HttpResponseRedirect(reverse('helpdesk:ticket_cc',
|
||||||
|
kwargs={'ticket_id': ticket.id}))
|
||||||
else:
|
else:
|
||||||
form = TicketCCForm()
|
form = TicketCCForm()
|
||||||
return render(request, template_name='helpdesk/ticket_cc_add.html',
|
return render(request, 'helpdesk/ticket_cc_add.html', {
|
||||||
context = {
|
|
||||||
'ticket': ticket,
|
'ticket': ticket,
|
||||||
'form': form,
|
'form': form,
|
||||||
})
|
})
|
||||||
ticket_cc_add = staff_member_required(ticket_cc_add)
|
ticket_cc_add = staff_member_required(ticket_cc_add)
|
||||||
|
|
||||||
|
|
||||||
def ticket_cc_del(request, ticket_id, cc_id):
|
def ticket_cc_del(request, ticket_id, cc_id):
|
||||||
cc = get_object_or_404(TicketCC, ticket__id=ticket_id, id=cc_id)
|
cc = get_object_or_404(TicketCC, ticket__id=ticket_id, id=cc_id)
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
cc.delete()
|
cc.delete()
|
||||||
return HttpResponseRedirect(reverse('helpdesk:ticket_cc', kwargs={'ticket_id': cc.ticket.id}))
|
return HttpResponseRedirect(reverse('helpdesk:ticket_cc',
|
||||||
return render(request, template_name='helpdesk/ticket_cc_del.html',
|
kwargs={'ticket_id': cc.ticket.id}))
|
||||||
context = {
|
return render(request, 'helpdesk/ticket_cc_del.html', {'cc': cc})
|
||||||
'cc': cc,
|
|
||||||
})
|
|
||||||
ticket_cc_del = staff_member_required(ticket_cc_del)
|
ticket_cc_del = staff_member_required(ticket_cc_del)
|
||||||
|
|
||||||
|
|
||||||
def ticket_dependency_add(request, ticket_id):
|
def ticket_dependency_add(request, ticket_id):
|
||||||
ticket = get_object_or_404(Ticket, id=ticket_id)
|
ticket = get_object_or_404(Ticket, id=ticket_id)
|
||||||
if not _has_access_to_queue(request.user, ticket.queue):
|
if not _has_access_to_queue(request.user, ticket.queue):
|
||||||
@ -1347,24 +1387,22 @@ def ticket_dependency_add(request, ticket_id):
|
|||||||
return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket.id]))
|
return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket.id]))
|
||||||
else:
|
else:
|
||||||
form = TicketDependencyForm()
|
form = TicketDependencyForm()
|
||||||
return render(request, template_name='helpdesk/ticket_dependency_add.html',
|
return render(request, 'helpdesk/ticket_dependency_add.html', {
|
||||||
context = {
|
|
||||||
'ticket': ticket,
|
'ticket': ticket,
|
||||||
'form': form,
|
'form': form,
|
||||||
})
|
})
|
||||||
ticket_dependency_add = staff_member_required(ticket_dependency_add)
|
ticket_dependency_add = staff_member_required(ticket_dependency_add)
|
||||||
|
|
||||||
|
|
||||||
def ticket_dependency_del(request, ticket_id, dependency_id):
|
def ticket_dependency_del(request, ticket_id, dependency_id):
|
||||||
dependency = get_object_or_404(TicketDependency, ticket__id=ticket_id, id=dependency_id)
|
dependency = get_object_or_404(TicketDependency, ticket__id=ticket_id, id=dependency_id)
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
dependency.delete()
|
dependency.delete()
|
||||||
return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket_id]))
|
return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket_id]))
|
||||||
return render(request, template_name='helpdesk/ticket_dependency_del.html',
|
return render(request, 'helpdesk/ticket_dependency_del.html', {'dependency': dependency})
|
||||||
context = {
|
|
||||||
'dependency': dependency,
|
|
||||||
})
|
|
||||||
ticket_dependency_del = staff_member_required(ticket_dependency_del)
|
ticket_dependency_del = staff_member_required(ticket_dependency_del)
|
||||||
|
|
||||||
|
|
||||||
def attachment_del(request, ticket_id, attachment_id):
|
def attachment_del(request, ticket_id, attachment_id):
|
||||||
ticket = get_object_or_404(Ticket, id=ticket_id)
|
ticket = get_object_or_404(Ticket, id=ticket_id)
|
||||||
if not _has_access_to_queue(request.user, ticket.queue):
|
if not _has_access_to_queue(request.user, ticket.queue):
|
||||||
@ -1374,6 +1412,7 @@ def attachment_del(request, ticket_id, attachment_id):
|
|||||||
return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket_id]))
|
return HttpResponseRedirect(reverse('helpdesk:view', args=[ticket_id]))
|
||||||
attachment_del = staff_member_required(attachment_del)
|
attachment_del = staff_member_required(attachment_del)
|
||||||
|
|
||||||
|
|
||||||
def calc_average_nbr_days_until_ticket_resolved(Tickets):
|
def calc_average_nbr_days_until_ticket_resolved(Tickets):
|
||||||
nbr_closed_tickets = len(Tickets)
|
nbr_closed_tickets = len(Tickets)
|
||||||
days_per_ticket = 0
|
days_per_ticket = 0
|
||||||
@ -1392,6 +1431,7 @@ def calc_average_nbr_days_until_ticket_resolved(Tickets):
|
|||||||
|
|
||||||
return mean_per_ticket
|
return mean_per_ticket
|
||||||
|
|
||||||
|
|
||||||
def calc_basic_ticket_stats(Tickets):
|
def calc_basic_ticket_stats(Tickets):
|
||||||
# all not closed tickets (open, reopened, resolved,) - independent of user
|
# all not closed tickets (open, reopened, resolved,) - independent of user
|
||||||
all_open_tickets = Tickets.exclude(status=Ticket.CLOSED_STATUS)
|
all_open_tickets = Tickets.exclude(status=Ticket.CLOSED_STATUS)
|
||||||
@ -1417,43 +1457,52 @@ def calc_basic_ticket_stats(Tickets):
|
|||||||
# (O)pen (T)icket (S)tats
|
# (O)pen (T)icket (S)tats
|
||||||
ots = list()
|
ots = list()
|
||||||
# label, number entries, color, sort_string
|
# label, number entries, color, sort_string
|
||||||
ots.append(['< 30 days', N_ota_le_30, get_color_for_nbr_days(N_ota_le_30), sort_string(date_30_str, ''), ])
|
ots.append(['< 30 days', N_ota_le_30, get_color_for_nbr_days(N_ota_le_30),
|
||||||
ots.append(['30 - 60 days', N_ota_le_60_ge_30, get_color_for_nbr_days(N_ota_le_60_ge_30), sort_string(date_60_str, date_30_str), ])
|
sort_string(date_30_str, ''), ])
|
||||||
ots.append(['> 60 days', N_ota_ge_60, get_color_for_nbr_days(N_ota_ge_60), sort_string('', date_60_str), ])
|
ots.append(['30 - 60 days', N_ota_le_60_ge_30, get_color_for_nbr_days(N_ota_le_60_ge_30),
|
||||||
|
sort_string(date_60_str, date_30_str), ])
|
||||||
|
ots.append(['> 60 days', N_ota_ge_60, get_color_for_nbr_days(N_ota_ge_60),
|
||||||
|
sort_string('', date_60_str), ])
|
||||||
|
|
||||||
# all closed tickets - independent of user.
|
# all closed tickets - independent of user.
|
||||||
all_closed_tickets = Tickets.filter(status=Ticket.CLOSED_STATUS)
|
all_closed_tickets = Tickets.filter(status=Ticket.CLOSED_STATUS)
|
||||||
average_nbr_days_until_ticket_closed = calc_average_nbr_days_until_ticket_resolved(all_closed_tickets)
|
average_nbr_days_until_ticket_closed = \
|
||||||
|
calc_average_nbr_days_until_ticket_resolved(all_closed_tickets)
|
||||||
# all closed tickets that were opened in the last 60 days.
|
# all closed tickets that were opened in the last 60 days.
|
||||||
all_closed_last_60_days = all_closed_tickets.filter(created__gte=date_60_str)
|
all_closed_last_60_days = all_closed_tickets.filter(created__gte=date_60_str)
|
||||||
average_nbr_days_until_ticket_closed_last_60_days = calc_average_nbr_days_until_ticket_resolved(all_closed_last_60_days)
|
average_nbr_days_until_ticket_closed_last_60_days = \
|
||||||
|
calc_average_nbr_days_until_ticket_resolved(all_closed_last_60_days)
|
||||||
|
|
||||||
# put together basic stats
|
# put together basic stats
|
||||||
basic_ticket_stats = { 'average_nbr_days_until_ticket_closed': average_nbr_days_until_ticket_closed,
|
basic_ticket_stats = {
|
||||||
'average_nbr_days_until_ticket_closed_last_60_days': average_nbr_days_until_ticket_closed_last_60_days,
|
'average_nbr_days_until_ticket_closed': average_nbr_days_until_ticket_closed,
|
||||||
'open_ticket_stats': ots, }
|
'average_nbr_days_until_ticket_closed_last_60_days':
|
||||||
|
average_nbr_days_until_ticket_closed_last_60_days,
|
||||||
|
'open_ticket_stats': ots,
|
||||||
|
}
|
||||||
|
|
||||||
return basic_ticket_stats
|
return basic_ticket_stats
|
||||||
|
|
||||||
|
|
||||||
def get_color_for_nbr_days(nbr_days):
|
def get_color_for_nbr_days(nbr_days):
|
||||||
''' '''
|
|
||||||
if nbr_days < 5:
|
if nbr_days < 5:
|
||||||
color_string = 'green'
|
color_string = 'green'
|
||||||
elif nbr_days >= 5 and nbr_days < 10:
|
elif nbr_days < 10:
|
||||||
color_string = 'orange'
|
color_string = 'orange'
|
||||||
else: # more than 10 days
|
else: # more than 10 days
|
||||||
color_string = 'red'
|
color_string = 'red'
|
||||||
|
|
||||||
return color_string
|
return color_string
|
||||||
|
|
||||||
|
|
||||||
def days_since_created(today, ticket):
|
def days_since_created(today, ticket):
|
||||||
return (today - ticket.created).days
|
return (today - ticket.created).days
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
return 'sort=created&date_from=%s&date_to=%s&status=%s&status=%s&status=%s' %(begin, end, Ticket.OPEN_STATUS, Ticket.REOPENED_STATUS, Ticket.RESOLVED_STATUS)
|
return 'sort=created&date_from=%s&date_to=%s&status=%s&status=%s&status=%s' % (
|
||||||
|
begin, end, Ticket.OPEN_STATUS, Ticket.REOPENED_STATUS, Ticket.RESOLVED_STATUS)
|
||||||
|
|
||||||
|
|
||||||
|
25
quicktest.py
25
quicktest.py
@ -38,6 +38,27 @@ class QuickDjangoTest(object):
|
|||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': (
|
||||||
|
# Defaults:
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.i18n",
|
||||||
|
"django.template.context_processors.media",
|
||||||
|
"django.template.context_processors.static",
|
||||||
|
"django.template.context_processors.tz",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
# Our extra:
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.apps = args
|
self.apps = args
|
||||||
# Get the version of the test suite
|
# Get the version of the test suite
|
||||||
@ -91,7 +112,8 @@ class QuickDjangoTest(object):
|
|||||||
INSTALLED_APPS=self.INSTALLED_APPS + self.apps,
|
INSTALLED_APPS=self.INSTALLED_APPS + self.apps,
|
||||||
MIDDLEWARE_CLASSES=self.MIDDLEWARE_CLASSES,
|
MIDDLEWARE_CLASSES=self.MIDDLEWARE_CLASSES,
|
||||||
ROOT_URLCONF='helpdesk.tests.urls',
|
ROOT_URLCONF='helpdesk.tests.urls',
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL='/static/',
|
||||||
|
TEMPLATES=self.TEMPLATES
|
||||||
)
|
)
|
||||||
|
|
||||||
# compatibility with django 1.8 downwards
|
# compatibility with django 1.8 downwards
|
||||||
@ -129,4 +151,3 @@ if __name__ == '__main__':
|
|||||||
parser.add_argument('apps', nargs='+', type=str)
|
parser.add_argument('apps', nargs='+', type=str)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
QuickDjangoTest(*args.apps)
|
QuickDjangoTest(*args.apps)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user