mirror of
https://gitea.mueller.network/extern/django-helpdesk.git
synced 2024-11-24 17:04:25 +01:00
Sync with namespace and other v0.2 changes
This commit is contained in:
commit
cad174468b
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 @@
|
|||||||
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
|
23
.travis.yml
23
.travis.yml
@ -6,18 +6,23 @@ python:
|
|||||||
- "3.5"
|
- "3.5"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- DJANGO=1.7.11
|
- DJANGO=1.8.15
|
||||||
- DJANGO=1.8.7
|
- DJANGO=1.9.10
|
||||||
- DJANGO=1.9
|
- DJANGO=1.10.2
|
||||||
|
|
||||||
matrix:
|
|
||||||
exclude:
|
|
||||||
- python: "3.5" # django 1.7 does not support python 3.5
|
|
||||||
env: DJANGO=1.7.11
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install argparse
|
- pip install argparse
|
||||||
|
- pip install coverage
|
||||||
|
- pip install codecov
|
||||||
|
- pip install pep8
|
||||||
- pip install -q Django==$DJANGO
|
- pip install -q Django==$DJANGO
|
||||||
- pip install -q -r requirements.txt
|
- pip install -q -r requirements.txt
|
||||||
|
|
||||||
script: python quicktest.py helpdesk
|
before_script:
|
||||||
|
- "pep8 --exclude=migrations,south_migrations --ignore=E501 helpdesk"
|
||||||
|
|
||||||
|
script:
|
||||||
|
- coverage run --source='.' quicktest.py helpdesk
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- codecov
|
||||||
|
21
AUTHORS
21
AUTHORS
@ -1,13 +1,24 @@
|
|||||||
django-helpdesk was originally written by Ross Poulton. Since publishing the
|
django-helpdesk was originally written by Ross Poulton. After nearly nine
|
||||||
code a number of people have made some fantastic improvements and provided
|
years under his guidance, the project graduated to its own eponymous
|
||||||
bug fixes and updates as the Django codebase has moved on and caused small
|
organization, and is currently maintained by:
|
||||||
portions of this application to break.
|
|
||||||
|
|
||||||
To these people, and any more, my sincere thanks:
|
Alex Seeholzer (@flinz)
|
||||||
|
Garret Wassermann (@gwasser)
|
||||||
|
Jonathan Barratt (@reduxionist)
|
||||||
|
|
||||||
|
Since publishing the code a number of people have made some fantastic
|
||||||
|
improvements and provided bug fixes and updates as the Django codebase
|
||||||
|
has moved on and caused small portions of this application to break.
|
||||||
|
|
||||||
|
To these people, and many more, our sincere thanks:
|
||||||
|
|
||||||
|
Alex Barcelo
|
||||||
Andreas Kotowicz
|
Andreas Kotowicz
|
||||||
Chris Etcp
|
Chris Etcp
|
||||||
|
Daryl Egarr
|
||||||
David Clymer
|
David Clymer
|
||||||
Loe Spee
|
Loe Spee
|
||||||
Maxim Litnitskiy
|
Maxim Litnitskiy
|
||||||
Nikolay Panov
|
Nikolay Panov
|
||||||
|
Stefano Brentegani
|
||||||
|
Tony Zhu
|
||||||
|
64
CHANGELOG
64
CHANGELOG
@ -1,64 +0,0 @@
|
|||||||
2009-09-09 r138 Issue #104 Add a CHANGELOG file
|
|
||||||
|
|
||||||
2009-09-09 r139 Issue #102 Add Ticket CC's
|
|
||||||
Add rudimentary CC: functionality on tickets, controlled by staff users. CC's
|
|
||||||
can be e-mail addresses or users, who will receive copies of all emails sent
|
|
||||||
to the Submitter. This is a work in progress.
|
|
||||||
|
|
||||||
2009-09-09 r140 Issue #13 Add Tags to tickets
|
|
||||||
Patch courtesy of david@zettazebra.com, adds the ability to add tags to
|
|
||||||
tickets if django-tagging is installed and in use. If django-tagging isn't
|
|
||||||
being used, no change is visible to the user.
|
|
||||||
|
|
||||||
2009-10-13 r141 Issue #118 Incorrect locale handling on email templates
|
|
||||||
Patch courtesy of hgeerts. Corrects the handling of locale on email
|
|
||||||
templaets, defaulting to English if no locale is provided.
|
|
||||||
|
|
||||||
2009-10-13 r142 Issue #117 Incorrect I18N usage in a few spots
|
|
||||||
Patch thanks to hgeerts.
|
|
||||||
|
|
||||||
2009-10-13 r143 Issue #113 - Clicking Queue names on the Dashboard was showing
|
|
||||||
all tickets; now only shows open tickets. Thanks to Andreas Kotowicz for the
|
|
||||||
sugestion.
|
|
||||||
|
|
||||||
2009-12-16 r144 Issue #122 - Infinite loop when most recent ticket was
|
|
||||||
opened in December. Thanks to Chris Vigelius for the report.
|
|
||||||
|
|
||||||
2009-12-16 r145 issue #123 - Google Chart doesn't show when there is a large
|
|
||||||
volume of data in the system. This patch restricts the chart to 1000px wide.
|
|
||||||
|
|
||||||
2009-12-16 r146 Issue #121 Formatting fix for email subjects. Thanks,
|
|
||||||
Andreas Kotowicz.
|
|
||||||
|
|
||||||
2009-12-16 r147 Issue #119 Update Russian translation, thanks to Alex Yakovlev
|
|
||||||
|
|
||||||
2009-12-23 r148 Issue #125 Errors occurring when running reports with no data
|
|
||||||
|
|
||||||
2010-01-20 r149 Issue #126 Reports didn't work with transalations.
|
|
||||||
|
|
||||||
2010-01-20 r150 Issue #127 Add german transalation, courtesy of openinformation.org
|
|
||||||
|
|
||||||
2009-01-20 r151 Issue #128 If queue name has a dash in it, email imported failed. Thanks to enix.org for the patch.
|
|
||||||
|
|
||||||
2010-01-21 r152 Fix indentation error caused by issue #126.
|
|
||||||
|
|
||||||
2010-01-26 r153 Fix issue #129 - can not 'unassign' tickets. Thanks to
|
|
||||||
lukeman for the patch.
|
|
||||||
|
|
||||||
2010-01-26 r154 Fix bug in the code from Issue #102 where TicketCC's couldn't
|
|
||||||
be deleted. Thanks again to lukeman.
|
|
||||||
|
|
||||||
2010-01-31 r155 Fix bug caused by issue #129 - ticket followup titles being
|
|
||||||
set incorrectly. Thanks to Lukeman for the fix.
|
|
||||||
|
|
||||||
2010-07-16 r157 Fix issues #141, #142 - IMAP infinite loops and ticket
|
|
||||||
pagination issues. Thanks to Walter Doekes for the patches.
|
|
||||||
|
|
||||||
2010-07-16 r158 New CSRF functionality for Django 1.1+. Thanks to
|
|
||||||
'litchfield4' for the patch.
|
|
||||||
|
|
||||||
2010-09-04 r159 Error when updating multiple tickets. Issue #135.
|
|
||||||
|
|
||||||
2010-09-04 r160 Fix translation blocks in deletion templates. Some translation strings will need to be updated. Thanks to william88 for the bug report.
|
|
||||||
|
|
||||||
2010-09-04 r161 Fix jQuery filename in public templates. Thanks to bruno.braga for the fix.
|
|
@ -1,7 +1,6 @@
|
|||||||
include README
|
include README.rst
|
||||||
include UPGRADE
|
include AUTHORS
|
||||||
include LICENSE*
|
include LICENSE*
|
||||||
include CHANGELOG
|
|
||||||
include requirements.txt
|
include requirements.txt
|
||||||
|
|
||||||
recursive-include helpdesk/static/helpdesk *
|
recursive-include helpdesk/static/helpdesk *
|
||||||
|
16
README.rst
16
README.rst
@ -1,13 +1,16 @@
|
|||||||
django-helpdesk - A Django powered ticket tracker for small businesses.
|
django-helpdesk - A Django powered ticket tracker for small businesses.
|
||||||
=======================================================================
|
=======================================================================
|
||||||
|
|
||||||
.. image:: https://travis-ci.org/rossp/django-helpdesk.png?branch=master
|
.. image:: https://travis-ci.org/django-helpdesk/django-helpdesk.png?branch=master
|
||||||
:target: https://travis-ci.org/rossp/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
|
||||||
company who originally created it. As of January 2011 the name has been
|
company which originally created it. As of January 2011 the name has been
|
||||||
changed to reflect what it really is: a Django-powered ticket tracker with
|
changed to reflect what it really is: a Django-powered ticket tracker with
|
||||||
contributors reaching far beyond Jutda.
|
contributors reaching far beyond Jutda.
|
||||||
|
|
||||||
@ -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.8+, preferably 1.10)
|
||||||
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`
|
||||||
@ -94,11 +97,8 @@ Contributing
|
|||||||
|
|
||||||
If you want to help translate django-helpdesk into languages other than English, we encourage you to make use of our Transifex project.
|
If you want to help translate django-helpdesk into languages other than English, we encourage you to make use of our Transifex project.
|
||||||
|
|
||||||
https://www.transifex.com/rossp/django-helpdesk/
|
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/rossp/django-helpdesk.png?branch=master
|
|
||||||
:target: https://travis-ci.org/rossp/django-helpdesk
|
|
||||||
|
@ -5,7 +5,7 @@ WORKDIR=/tmp/django-helpdesk-build.$$
|
|||||||
mkdir $WORKDIR
|
mkdir $WORKDIR
|
||||||
pushd $WORKDIR
|
pushd $WORKDIR
|
||||||
|
|
||||||
git clone git://github.com/rossp/django-helpdesk.git
|
git clone git://github.com/django-helpdesk/django-helpdesk.git
|
||||||
cd django-helpdesk
|
cd django-helpdesk
|
||||||
|
|
||||||
/usr/bin/python setup.py sdist upload
|
/usr/bin/python setup.py sdist upload
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
Ticket API
|
|
||||||
==========
|
|
||||||
|
|
||||||
*Warning*: The django-helpdesk API is deprecated, and no longer maintained. See https://github.com/rossp/django-helpdesk/issues/198 for more details.
|
|
||||||
|
|
||||||
The API will be removed in January 2016 - you should instead build an integration with eg django-rest-framework.
|
|
||||||
|
|
||||||
For details on the current API including usage instructions and command syntax, see the file ``templates/helpdesk/api_help.html``, or visit http://helpdesk/api/help/.
|
|
@ -32,7 +32,7 @@ Wherever possible please break git commits up into small chunks that are specifi
|
|||||||
|
|
||||||
Commit messages should also explain *what*, precisely, has been changed.
|
Commit messages should also explain *what*, precisely, has been changed.
|
||||||
|
|
||||||
If you have any questions, please contact the project co-ordinator, Ross Poulton, at ross@rossp.org.
|
If you have any questions, please start a discussion on the GitHub issue tracker at https://github.com/django-helpdesk/django-helpdesk/issues
|
||||||
|
|
||||||
Tests
|
Tests
|
||||||
-----
|
-----
|
||||||
|
@ -16,7 +16,6 @@ Contents
|
|||||||
settings
|
settings
|
||||||
spam
|
spam
|
||||||
custom_fields
|
custom_fields
|
||||||
api
|
|
||||||
contributing
|
contributing
|
||||||
|
|
||||||
|
|
||||||
@ -49,7 +48,7 @@ Customers (who are not 'staff' users in Django) can:
|
|||||||
3. Review open and closed requests they submitted
|
3. Review open and closed requests they submitted
|
||||||
|
|
||||||
Staff Capabilities
|
Staff Capabilities
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
If a user is a staff member, they get general helpdesk access, including:
|
If a user is a staff member, they get general helpdesk access, including:
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ Try using ``pip install django-helpdesk``. Go and have a beer to celebrate Pytho
|
|||||||
GIT Checkout (Cutting Edge)
|
GIT Checkout (Cutting Edge)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
If you're planning on editing the code or just want to get whatever is the latest and greatest, you can clone the official Git repository with ``git clone git://github.com/rossp/django-helpdesk.git``
|
If you're planning on editing the code or just want to get whatever is the latest and greatest, you can clone the official Git repository with ``git clone git://github.com/django-helpdesk/django-helpdesk.git``
|
||||||
|
|
||||||
Copy the ``helpdesk`` folder into your ``PYTHONPATH``.
|
Copy the ``helpdesk`` folder into your ``PYTHONPATH``.
|
||||||
|
|
||||||
@ -49,10 +49,13 @@ Adding To Your Django Project
|
|||||||
|
|
||||||
Note that you can change 'helpdesk/' to anything you like, such as 'support/' or 'help/'. If you want django-helpdesk to be available at the root of your site (for example at http://support.mysite.tld/) then the line will be as follows::
|
Note that you can change 'helpdesk/' to anything you like, such as 'support/' or 'help/'. If you want django-helpdesk to be available at the root of your site (for example at http://support.mysite.tld/) then the line will be as follows::
|
||||||
|
|
||||||
url(r'', include('helpdesk.urls')),
|
url(r'', include('helpdesk.urls', namespace='helpdesk')),
|
||||||
|
|
||||||
This line will have to come *after* any other lines in your urls.py such as those used by the Django admin.
|
This line will have to come *after* any other lines in your urls.py such as those used by the Django admin.
|
||||||
|
|
||||||
|
Note that the `helpdesk` namespace is no longer required for Django 1.9 and you can use a different namespace.
|
||||||
|
However, it is recommended to use the default namespace name for clarity.
|
||||||
|
|
||||||
3. Create the required database tables.
|
3. Create the required database tables.
|
||||||
|
|
||||||
Migrate using Django migrations::
|
Migrate using Django migrations::
|
||||||
|
@ -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
|
||||||
@ -23,7 +42,6 @@ If you want to override the default settings for your users, create ``HELPDESK_D
|
|||||||
'email_on_ticket_assign': True,
|
'email_on_ticket_assign': True,
|
||||||
'email_on_ticket_change': True,
|
'email_on_ticket_change': True,
|
||||||
'login_view_ticketlist': True,
|
'login_view_ticketlist': True,
|
||||||
'email_on_ticket_apichange': True,
|
|
||||||
'tickets_per_page': 25
|
'tickets_per_page': 25
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +86,10 @@ These changes are visible throughout django-helpdesk
|
|||||||
|
|
||||||
**Default:** ``HELPDESK_EMAIL_SUBJECT_TEMPLATE = "{{ ticket.ticket }} {{ ticket.title|safe }} %(subject)s"``
|
**Default:** ``HELPDESK_EMAIL_SUBJECT_TEMPLATE = "{{ ticket.ticket }} {{ ticket.title|safe }} %(subject)s"``
|
||||||
|
|
||||||
|
- **HELPDESK_EMAIL_FALLBACK_LOCALE** Fallback locale for templated emails when queue locale not found
|
||||||
|
|
||||||
|
**Default:** ``HELPDESK_EMAIL_FALLBACK_LOCALE= "en"``
|
||||||
|
|
||||||
|
|
||||||
Options shown on public pages
|
Options shown on public pages
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
@ -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)
|
|
||||||
|
@ -24,7 +24,7 @@ You should pass in the keyword argument 'agent' to the name of your program,
|
|||||||
when you create an Akismet instance. This sets the ``user-agent`` to a useful
|
when you create an Akismet instance. This sets the ``user-agent`` to a useful
|
||||||
value.
|
value.
|
||||||
|
|
||||||
The default is : ::
|
The default is::
|
||||||
|
|
||||||
Python Interface by Fuzzyman | akismet.py/0.2.0
|
Python Interface by Fuzzyman | akismet.py/0.2.0
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ Usage example::
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import os, sys
|
import os
|
||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
@ -70,7 +70,7 @@ __all__ = (
|
|||||||
'Akismet',
|
'Akismet',
|
||||||
'AkismetError',
|
'AkismetError',
|
||||||
'APIKeyError',
|
'APIKeyError',
|
||||||
)
|
)
|
||||||
|
|
||||||
__author__ = 'Michael Foord <fuzzyman AT voidspace DOT org DOT uk>'
|
__author__ = 'Michael Foord <fuzzyman AT voidspace DOT org DOT uk>'
|
||||||
|
|
||||||
@ -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,15 +132,13 @@ 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)
|
||||||
except Exception, e:
|
except Exception as e:
|
||||||
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.
|
||||||
@ -151,7 +152,7 @@ class Akismet(object):
|
|||||||
"""
|
"""
|
||||||
if key is None and isfile('apikey.txt'):
|
if key is None and isfile('apikey.txt'):
|
||||||
the_file = [l.strip() for l in open('apikey.txt').readlines()
|
the_file = [l.strip() for l in open('apikey.txt').readlines()
|
||||||
if l.strip() and not l.strip().startswith('#')]
|
if l.strip() and not l.strip().startswith('#')]
|
||||||
try:
|
try:
|
||||||
self.key = the_file[0]
|
self.key = the_file[0]
|
||||||
self.blog_url = the_file[1]
|
self.blog_url = the_file[1]
|
||||||
@ -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.
|
||||||
@ -179,12 +179,12 @@ class Akismet(object):
|
|||||||
"""
|
"""
|
||||||
if self.key is None:
|
if self.key is None:
|
||||||
raise APIKeyError("Your have not set an API key.")
|
raise APIKeyError("Your have not set an API key.")
|
||||||
data = { 'key': self.key, 'blog': self.blog_url }
|
data = {'key': self.key, 'blog': self.blog_url}
|
||||||
# this function *doesn't* use the key as part of the URL
|
# this function *doesn't* use the key as part of the URL
|
||||||
url = 'http://%sverify-key' % self.baseurl
|
url = 'http://%sverify-key' % self.baseurl
|
||||||
# we *don't* trap the error here
|
# we *don't* trap the error here
|
||||||
# so if akismet is down it will raise an HTTPError or URLError
|
# so if akismet is down it will raise an HTTPError or URLError
|
||||||
headers = {'User-Agent' : self.user_agent}
|
headers = {'User-Agent': self.user_agent}
|
||||||
resp = self._safeRequest(url, urlencode(data), headers)
|
resp = self._safeRequest(url, urlencode(data), headers)
|
||||||
if resp.lower() == 'valid':
|
if resp.lower() == 'valid':
|
||||||
return True
|
return True
|
||||||
@ -203,13 +203,13 @@ class Akismet(object):
|
|||||||
worked out.
|
worked out.
|
||||||
"""
|
"""
|
||||||
data['comment_content'] = comment
|
data['comment_content'] = comment
|
||||||
if not 'user_ip' in data:
|
if 'user_ip' not in data:
|
||||||
try:
|
try:
|
||||||
val = os.environ['REMOTE_ADDR']
|
val = os.environ['REMOTE_ADDR']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise AkismetError("No 'user_ip' supplied")
|
raise AkismetError("No 'user_ip' supplied")
|
||||||
data['user_ip'] = val
|
data['user_ip'] = val
|
||||||
if not 'user_agent' in data:
|
if 'user_agent' not in data:
|
||||||
try:
|
try:
|
||||||
val = os.environ['HTTP_USER_AGENT']
|
val = os.environ['HTTP_USER_AGENT']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -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.
|
||||||
@ -316,7 +313,7 @@ class Akismet(object):
|
|||||||
url = '%scomment-check' % self._getURL()
|
url = '%scomment-check' % self._getURL()
|
||||||
# we *don't* trap the error here
|
# we *don't* trap the error here
|
||||||
# so if akismet is down it will raise an HTTPError or URLError
|
# so if akismet is down it will raise an HTTPError or URLError
|
||||||
headers = {'User-Agent' : self.user_agent}
|
headers = {'User-Agent': self.user_agent}
|
||||||
resp = self._safeRequest(url, urlencode(data), headers)
|
resp = self._safeRequest(url, urlencode(data), headers)
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
return resp
|
return resp
|
||||||
@ -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,
|
||||||
@ -347,10 +343,9 @@ class Akismet(object):
|
|||||||
url = '%ssubmit-spam' % self._getURL()
|
url = '%ssubmit-spam' % self._getURL()
|
||||||
# we *don't* trap the error here
|
# we *don't* trap the error here
|
||||||
# so if akismet is down it will raise an HTTPError or URLError
|
# so if akismet is down it will raise an HTTPError or URLError
|
||||||
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,
|
||||||
@ -368,5 +363,5 @@ class Akismet(object):
|
|||||||
url = '%ssubmit-ham' % self._getURL()
|
url = '%ssubmit-ham' % self._getURL()
|
||||||
# we *don't* trap the error here
|
# we *don't* trap the error here
|
||||||
# so if akismet is down it will raise an HTTPError or URLError
|
# so if akismet is down it will raise an HTTPError or URLError
|
||||||
headers = {'User-Agent' : self.user_agent}
|
headers = {'User-Agent': self.user_agent}
|
||||||
self._safeRequest(url, urlencode(data), headers)
|
self._safeRequest(url, urlencode(data), headers)
|
||||||
|
@ -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
|
||||||
@ -52,7 +54,7 @@ class CustomFieldMixin(object):
|
|||||||
fieldclass = forms.ChoiceField
|
fieldclass = forms.ChoiceField
|
||||||
choices = field.choices_as_array
|
choices = field.choices_as_array
|
||||||
if field.empty_selection_list:
|
if field.empty_selection_list:
|
||||||
choices.insert(0, ('','---------' ) )
|
choices.insert(0, ('', '---------'))
|
||||||
instanceargs['choices'] = choices
|
instanceargs['choices'] = choices
|
||||||
elif field.data_type == 'boolean':
|
elif field.data_type == 'boolean':
|
||||||
fieldclass = forms.BooleanField
|
fieldclass = forms.BooleanField
|
||||||
@ -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')
|
||||||
@ -91,15 +95,14 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm):
|
|||||||
except TicketCustomFieldValue.DoesNotExist:
|
except TicketCustomFieldValue.DoesNotExist:
|
||||||
initial_value = None
|
initial_value = None
|
||||||
instanceargs = {
|
instanceargs = {
|
||||||
'label': field.label,
|
'label': field.label,
|
||||||
'help_text': field.help_text,
|
'help_text': field.help_text,
|
||||||
'required': field.required,
|
'required': field.required,
|
||||||
'initial': initial_value,
|
'initial': initial_value,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.customfield_to_field(field, instanceargs)
|
self.customfield_to_field(field, instanceargs)
|
||||||
|
|
||||||
|
|
||||||
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,42 +120,45 @@ 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(
|
||||||
widget=forms.Select(attrs={'class':'form-control'}),
|
widget=forms.Select(attrs={'class':'form-control'}),
|
||||||
label=_('Queue'),
|
label=_('Queue'),
|
||||||
required=True,
|
required=True,
|
||||||
choices=()
|
choices=()
|
||||||
)
|
)
|
||||||
|
|
||||||
title = forms.CharField(
|
title = forms.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
required=True,
|
required=True,
|
||||||
widget=forms.TextInput(attrs={'class':'form-control'}),
|
widget=forms.TextInput(attrs={'class':'form-control'}),
|
||||||
label=_('Summary of the problem'),
|
label=_('Summary of the problem'),
|
||||||
)
|
)
|
||||||
|
|
||||||
submitter_email = forms.EmailField(
|
submitter_email = forms.EmailField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Submitter E-Mail Address'),
|
label=_('Submitter E-Mail Address'),
|
||||||
widget=forms.TextInput(attrs={'class':'form-control'}),
|
widget=forms.TextInput(attrs={'class':'form-control'}),
|
||||||
help_text=_('This e-mail address will receive copies of all public '
|
help_text=_('This e-mail address will receive copies of all public '
|
||||||
'updates to this ticket.'),
|
'updates to this ticket.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
body = forms.CharField(
|
body = forms.CharField(
|
||||||
widget=forms.Textarea(attrs={'class':'form-control', 'rows': 15}),
|
widget=forms.Textarea(attrs={'class':'form-control', 'rows': 15}),
|
||||||
label=_('Description of Issue'),
|
label=_('Description of Issue'),
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
assigned_to = forms.ChoiceField(
|
assigned_to = forms.ChoiceField(
|
||||||
widget=forms.Select(attrs={'class':'form-control'}),
|
widget=forms.Select(attrs={'class':'form-control'}),
|
||||||
@ -160,8 +166,8 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
required=False,
|
required=False,
|
||||||
label=_('Case owner'),
|
label=_('Case owner'),
|
||||||
help_text=_('If you select an owner other than yourself, they\'ll be '
|
help_text=_('If you select an owner other than yourself, they\'ll be '
|
||||||
'e-mailed details of this ticket immediately.'),
|
'e-mailed details of this ticket immediately.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
priority = forms.ChoiceField(
|
priority = forms.ChoiceField(
|
||||||
widget=forms.Select(attrs={'class':'form-control'}),
|
widget=forms.Select(attrs={'class':'form-control'}),
|
||||||
@ -169,28 +175,27 @@ 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(
|
||||||
widget=forms.TextInput(attrs={'class':'form-control'}),
|
widget=forms.TextInput(attrs={'class':'form-control'}),
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Due on'),
|
label=_('Due on'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def clean_due_date(self):
|
def clean_due_date(self):
|
||||||
data = self.cleaned_data['due_date']
|
data = self.cleaned_data['due_date']
|
||||||
#TODO: add Google calendar update hook
|
# TODO: add Google calendar update hook
|
||||||
#if not hasattr(self, 'instance') or self.instance.due_date != new_data:
|
# if not hasattr(self, 'instance') or self.instance.due_date != new_data:
|
||||||
# print "you changed!"
|
# print "you changed!"
|
||||||
return data
|
return data
|
||||||
|
|
||||||
attachment = forms.FileField(
|
attachment = forms.FileField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Attach File'),
|
label=_('Attach File'),
|
||||||
help_text=_('You can attach a file such as a document or screenshot to this ticket.'),
|
help_text=_('You can attach a file such as a document or screenshot to this ticket.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -199,14 +204,13 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
super(TicketForm, self).__init__(*args, **kwargs)
|
super(TicketForm, self).__init__(*args, **kwargs)
|
||||||
for field in CustomField.objects.all():
|
for field in CustomField.objects.all():
|
||||||
instanceargs = {
|
instanceargs = {
|
||||||
'label': field.label,
|
'label': field.label,
|
||||||
'help_text': field.help_text,
|
'help_text': field.help_text,
|
||||||
'required': field.required,
|
'required': field.required,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.customfield_to_field(field, instanceargs)
|
self.customfield_to_field(field, instanceargs)
|
||||||
|
|
||||||
|
|
||||||
def save(self, user):
|
def save(self, user):
|
||||||
"""
|
"""
|
||||||
Writes and returns a Ticket() object
|
Writes and returns a Ticket() object
|
||||||
@ -214,15 +218,15 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
|
|
||||||
q = Queue.objects.get(id=int(self.cleaned_data['queue']))
|
q = Queue.objects.get(id=int(self.cleaned_data['queue']))
|
||||||
|
|
||||||
t = Ticket( title = self.cleaned_data['title'],
|
t = Ticket(title=self.cleaned_data['title'],
|
||||||
submitter_email = self.cleaned_data['submitter_email'],
|
submitter_email=self.cleaned_data['submitter_email'],
|
||||||
created = timezone.now(),
|
created=timezone.now(),
|
||||||
status = Ticket.OPEN_STATUS,
|
status=Ticket.OPEN_STATUS,
|
||||||
queue = q,
|
queue=q,
|
||||||
description = self.cleaned_data['body'],
|
description=self.cleaned_data['body'],
|
||||||
priority = self.cleaned_data['priority'],
|
priority=self.cleaned_data['priority'],
|
||||||
due_date = self.cleaned_data['due_date'],
|
due_date=self.cleaned_data['due_date'],
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.cleaned_data['assigned_to']:
|
if self.cleaned_data['assigned_to']:
|
||||||
try:
|
try:
|
||||||
@ -237,16 +241,16 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
field_name = field.replace('custom_', '', 1)
|
field_name = field.replace('custom_', '', 1)
|
||||||
customfield = CustomField.objects.get(name=field_name)
|
customfield = CustomField.objects.get(name=field_name)
|
||||||
cfv = TicketCustomFieldValue(ticket=t,
|
cfv = TicketCustomFieldValue(ticket=t,
|
||||||
field=customfield,
|
field=customfield,
|
||||||
value=value)
|
value=value)
|
||||||
cfv.save()
|
cfv.save()
|
||||||
|
|
||||||
f = FollowUp( ticket = t,
|
f = FollowUp(ticket=t,
|
||||||
title = _('Ticket Opened'),
|
title=_('Ticket Opened'),
|
||||||
date = timezone.now(),
|
date=timezone.now(),
|
||||||
public = True,
|
public=True,
|
||||||
comment = self.cleaned_data['body'],
|
comment=self.cleaned_data['body'],
|
||||||
user = user,
|
user=user,
|
||||||
)
|
)
|
||||||
if self.cleaned_data['assigned_to']:
|
if self.cleaned_data['assigned_to']:
|
||||||
f.title = _('Ticket Opened & Assigned to %(name)s') % {
|
f.title = _('Ticket Opened & Assigned to %(name)s') % {
|
||||||
@ -265,7 +269,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
filename=filename,
|
filename=filename,
|
||||||
mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream',
|
mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream',
|
||||||
size=file.size,
|
size=file.size,
|
||||||
)
|
)
|
||||||
a.file.save(file.name, file, save=False)
|
a.file.save(file.name, file, save=False)
|
||||||
a.save()
|
a.save()
|
||||||
|
|
||||||
@ -290,10 +294,14 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
sender=q.from_address,
|
sender=q.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(t.submitter_email)
|
messages_sent_to.append(t.submitter_email)
|
||||||
|
|
||||||
if t.assigned_to and 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,
|
||||||
@ -301,7 +309,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
sender=q.from_address,
|
sender=q.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(t.assigned_to.email)
|
messages_sent_to.append(t.assigned_to.email)
|
||||||
|
|
||||||
if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to:
|
if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to:
|
||||||
@ -312,10 +320,12 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
sender=q.from_address,
|
sender=q.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(q.new_ticket_cc)
|
messages_sent_to.append(q.new_ticket_cc)
|
||||||
|
|
||||||
if q.updated_ticket_cc and 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,
|
||||||
@ -323,7 +333,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
|
|||||||
sender=q.from_address,
|
sender=q.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
|
|
||||||
return t
|
return t
|
||||||
|
|
||||||
@ -333,28 +343,28 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
label=_('Queue'),
|
label=_('Queue'),
|
||||||
required=True,
|
required=True,
|
||||||
choices=()
|
choices=()
|
||||||
)
|
)
|
||||||
|
|
||||||
title = forms.CharField(
|
title = forms.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
required=True,
|
required=True,
|
||||||
widget=forms.TextInput(),
|
widget=forms.TextInput(),
|
||||||
label=_('Summary of your query'),
|
label=_('Summary of your query'),
|
||||||
)
|
)
|
||||||
|
|
||||||
submitter_email = forms.EmailField(
|
submitter_email = forms.EmailField(
|
||||||
required=True,
|
required=True,
|
||||||
label=_('Your E-Mail Address'),
|
label=_('Your E-Mail Address'),
|
||||||
help_text=_('We will e-mail you when your ticket is updated.'),
|
help_text=_('We will e-mail you when your ticket is updated.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
body = forms.CharField(
|
body = forms.CharField(
|
||||||
widget=forms.Textarea(),
|
widget=forms.Textarea(),
|
||||||
label=_('Description of your issue'),
|
label=_('Description of your issue'),
|
||||||
required=True,
|
required=True,
|
||||||
help_text=_('Please be as descriptive as possible, including any '
|
help_text=_('Please be as descriptive as possible, including any '
|
||||||
'details we may need to address your query.'),
|
'details we may need to address your query.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
priority = forms.ChoiceField(
|
priority = forms.ChoiceField(
|
||||||
choices=Ticket.PRIORITY_CHOICES,
|
choices=Ticket.PRIORITY_CHOICES,
|
||||||
@ -362,20 +372,20 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
initial='3',
|
initial='3',
|
||||||
label=_('Urgency'),
|
label=_('Urgency'),
|
||||||
help_text=_('Please select a priority carefully.'),
|
help_text=_('Please select a priority carefully.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
due_date = forms.DateTimeField(
|
due_date = forms.DateTimeField(
|
||||||
widget=extras.SelectDateWidget,
|
widget=extras.SelectDateWidget,
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Due on'),
|
label=_('Due on'),
|
||||||
)
|
)
|
||||||
|
|
||||||
attachment = forms.FileField(
|
attachment = forms.FileField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Attach File'),
|
label=_('Attach File'),
|
||||||
help_text=_('You can attach a file such as a document or screenshot to this ticket.'),
|
help_text=_('You can attach a file such as a document or screenshot to this ticket.'),
|
||||||
max_length=1000,
|
max_length=1000,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -384,10 +394,10 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
super(PublicTicketForm, self).__init__(*args, **kwargs)
|
super(PublicTicketForm, self).__init__(*args, **kwargs)
|
||||||
for field in CustomField.objects.filter(staff_only=False):
|
for field in CustomField.objects.filter(staff_only=False):
|
||||||
instanceargs = {
|
instanceargs = {
|
||||||
'label': field.label,
|
'label': field.label,
|
||||||
'help_text': field.help_text,
|
'help_text': field.help_text,
|
||||||
'required': field.required,
|
'required': field.required,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.customfield_to_field(field, instanceargs)
|
self.customfield_to_field(field, instanceargs)
|
||||||
|
|
||||||
@ -399,15 +409,15 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
q = Queue.objects.get(id=int(self.cleaned_data['queue']))
|
q = Queue.objects.get(id=int(self.cleaned_data['queue']))
|
||||||
|
|
||||||
t = Ticket(
|
t = Ticket(
|
||||||
title = self.cleaned_data['title'],
|
title=self.cleaned_data['title'],
|
||||||
submitter_email = self.cleaned_data['submitter_email'],
|
submitter_email=self.cleaned_data['submitter_email'],
|
||||||
created = timezone.now(),
|
created=timezone.now(),
|
||||||
status = Ticket.OPEN_STATUS,
|
status=Ticket.OPEN_STATUS,
|
||||||
queue = q,
|
queue=q,
|
||||||
description = self.cleaned_data['body'],
|
description=self.cleaned_data['body'],
|
||||||
priority = self.cleaned_data['priority'],
|
priority=self.cleaned_data['priority'],
|
||||||
due_date = self.cleaned_data['due_date'],
|
due_date=self.cleaned_data['due_date'],
|
||||||
)
|
)
|
||||||
|
|
||||||
if q.default_owner and not t.assigned_to:
|
if q.default_owner and not t.assigned_to:
|
||||||
t.assigned_to = q.default_owner
|
t.assigned_to = q.default_owner
|
||||||
@ -419,17 +429,17 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
field_name = field.replace('custom_', '', 1)
|
field_name = field.replace('custom_', '', 1)
|
||||||
customfield = CustomField.objects.get(name=field_name)
|
customfield = CustomField.objects.get(name=field_name)
|
||||||
cfv = TicketCustomFieldValue(ticket=t,
|
cfv = TicketCustomFieldValue(ticket=t,
|
||||||
field=customfield,
|
field=customfield,
|
||||||
value=value)
|
value=value)
|
||||||
cfv.save()
|
cfv.save()
|
||||||
|
|
||||||
f = FollowUp(
|
f = FollowUp(
|
||||||
ticket = t,
|
ticket=t,
|
||||||
title = _('Ticket Opened Via Web'),
|
title=_('Ticket Opened Via Web'),
|
||||||
date = timezone.now(),
|
date=timezone.now(),
|
||||||
public = True,
|
public=True,
|
||||||
comment = self.cleaned_data['body'],
|
comment=self.cleaned_data['body'],
|
||||||
)
|
)
|
||||||
|
|
||||||
f.save()
|
f.save()
|
||||||
|
|
||||||
@ -443,7 +453,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
filename=filename,
|
filename=filename,
|
||||||
mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream',
|
mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream',
|
||||||
size=file.size,
|
size=file.size,
|
||||||
)
|
)
|
||||||
a.file.save(file.name, file, save=False)
|
a.file.save(file.name, file, save=False)
|
||||||
a.save()
|
a.save()
|
||||||
|
|
||||||
@ -463,10 +473,13 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
sender=q.from_address,
|
sender=q.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(t.submitter_email)
|
messages_sent_to.append(t.submitter_email)
|
||||||
|
|
||||||
if t.assigned_to and 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,
|
||||||
@ -474,7 +487,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
sender=q.from_address,
|
sender=q.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(t.assigned_to.email)
|
messages_sent_to.append(t.assigned_to.email)
|
||||||
|
|
||||||
if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to:
|
if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to:
|
||||||
@ -485,10 +498,12 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
sender=q.from_address,
|
sender=q.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
messages_sent_to.append(q.new_ticket_cc)
|
messages_sent_to.append(q.new_ticket_cc)
|
||||||
|
|
||||||
if q.updated_ticket_cc and 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,
|
||||||
@ -496,7 +511,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
|
|||||||
sender=q.from_address,
|
sender=q.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
|
|
||||||
return t
|
return t
|
||||||
|
|
||||||
@ -506,25 +521,19 @@ class UserSettingsForm(forms.Form):
|
|||||||
label=_('Show Ticket List on Login?'),
|
label=_('Show Ticket List on Login?'),
|
||||||
help_text=_('Display the ticket list upon login? Otherwise, the dashboard is shown.'),
|
help_text=_('Display the ticket list upon login? Otherwise, the dashboard is shown.'),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
email_on_ticket_change = forms.BooleanField(
|
email_on_ticket_change = forms.BooleanField(
|
||||||
label=_('E-mail me on ticket change?'),
|
label=_('E-mail me on ticket change?'),
|
||||||
help_text=_('If you\'re the ticket owner and the ticket is changed via the web by somebody else, do you want to receive an e-mail?'),
|
help_text=_('If you\'re the ticket owner and the ticket is changed via the web by somebody else, do you want to receive an e-mail?'),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
email_on_ticket_assign = forms.BooleanField(
|
email_on_ticket_assign = forms.BooleanField(
|
||||||
label=_('E-mail me when assigned a ticket?'),
|
label=_('E-mail me when assigned a ticket?'),
|
||||||
help_text=_('If you are assigned a ticket via the web, do you want to receive an e-mail?'),
|
help_text=_('If you are assigned a ticket via the web, do you want to receive an e-mail?'),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
email_on_ticket_apichange = forms.BooleanField(
|
|
||||||
label=_('E-mail me when a ticket is changed via the API?'),
|
|
||||||
help_text=_('If a ticket is altered by the API, do you want to receive an e-mail?'),
|
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
tickets_per_page = forms.ChoiceField(
|
tickets_per_page = forms.ChoiceField(
|
||||||
label=_('Number of tickets to show per page'),
|
label=_('Number of tickets to show per page'),
|
||||||
@ -537,15 +546,23 @@ class UserSettingsForm(forms.Form):
|
|||||||
label=_('Use my e-mail address when submitting tickets?'),
|
label=_('Use my e-mail address when submitting tickets?'),
|
||||||
help_text=_('When you submit a ticket, do you want to automatically use your e-mail address as the submitter address? You can type a different e-mail address when entering the ticket if needed, this option only changes the default.'),
|
help_text=_('When you submit a ticket, do you want to automatically use your e-mail address as the submitter address? You can type a different e-mail address when entering the ticket if needed, this option only changes the default.'),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class EmailIgnoreForm(forms.ModelForm):
|
class EmailIgnoreForm(forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IgnoreEmail
|
model = IgnoreEmail
|
||||||
exclude = []
|
exclude = []
|
||||||
|
|
||||||
|
|
||||||
class TicketCCForm(forms.ModelForm):
|
class TicketCCForm(forms.ModelForm):
|
||||||
''' Adds either an email address or helpdesk user as a CC on a Ticket. Used for processing POST requests. '''
|
''' Adds either an email address or helpdesk user as a CC on a Ticket. Used for processing POST requests. '''
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = TicketCC
|
||||||
|
exclude = ('ticket',)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
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:
|
||||||
@ -553,9 +570,6 @@ 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 TicketCCUserForm(forms.ModelForm):
|
class TicketCCUserForm(forms.ModelForm):
|
||||||
''' Adds a helpdesk user as a CC on a Ticket '''
|
''' Adds a helpdesk user as a CC on a Ticket '''
|
||||||
@ -579,6 +593,7 @@ class TicketCCEmailForm(forms.ModelForm):
|
|||||||
exclude = ('ticket','user',)
|
exclude = ('ticket','user',)
|
||||||
|
|
||||||
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
|
||||||
@ -56,7 +63,8 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b
|
|||||||
from django.template import loader, Context
|
from django.template import loader, Context
|
||||||
|
|
||||||
from helpdesk.models import EmailTemplate
|
from helpdesk.models import EmailTemplate
|
||||||
from helpdesk.settings import HELPDESK_EMAIL_SUBJECT_TEMPLATE
|
from helpdesk.settings import HELPDESK_EMAIL_SUBJECT_TEMPLATE, \
|
||||||
|
HELPDESK_EMAIL_FALLBACK_LOCALE
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# RemovedInDjango110Warning: render() must be called with a dict, not a Context.
|
# RemovedInDjango110Warning: render() must be called with a dict, not a Context.
|
||||||
@ -68,9 +76,9 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b
|
|||||||
if hasattr(context['queue'], 'locale'):
|
if hasattr(context['queue'], 'locale'):
|
||||||
locale = getattr(context['queue'], 'locale', '')
|
locale = getattr(context['queue'], 'locale', '')
|
||||||
else:
|
else:
|
||||||
locale = context['queue'].get('locale', 'en')
|
locale = context['queue'].get('locale', HELPDESK_EMAIL_FALLBACK_LOCALE)
|
||||||
if not locale:
|
if not locale:
|
||||||
locale = 'en'
|
locale = HELPDESK_EMAIL_FALLBACK_LOCALE
|
||||||
|
|
||||||
t = None
|
t = None
|
||||||
try:
|
try:
|
||||||
@ -82,16 +90,17 @@ 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
|
||||||
|
|
||||||
if not sender:
|
if not sender:
|
||||||
sender = settings.DEFAULT_FROM_EMAIL
|
sender = settings.DEFAULT_FROM_EMAIL
|
||||||
|
|
||||||
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
|
||||||
@ -100,25 +109,26 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b
|
|||||||
|
|
||||||
text_part = template_func(
|
text_part = template_func(
|
||||||
"%s{%% include '%s' %%}" % (t.plain_text, footer_file)
|
"%s{%% include '%s' %%}" % (t.plain_text, footer_file)
|
||||||
).render(context)
|
).render(context)
|
||||||
|
|
||||||
email_html_base_file = os.path.join('helpdesk', locale, 'email_html_base.html')
|
email_html_base_file = os.path.join('helpdesk', locale, 'email_html_base.html')
|
||||||
|
|
||||||
|
# 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,
|
||||||
@ -128,13 +138,11 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b
|
|||||||
if recipients.find(','):
|
if recipients.find(','):
|
||||||
recipients = recipients.split(',')
|
recipients = recipients.split(',')
|
||||||
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:
|
||||||
@ -225,23 +233,23 @@ def safe_template_context(ticket):
|
|||||||
|
|
||||||
context = {
|
context = {
|
||||||
'queue': {},
|
'queue': {},
|
||||||
'ticket': {},
|
'ticket': {}
|
||||||
}
|
}
|
||||||
queue = ticket.queue
|
queue = ticket.queue
|
||||||
|
|
||||||
for field in ( 'title', 'slug', 'email_address', 'from_address', 'locale'):
|
for field in ('title', 'slug', 'email_address', 'from_address', 'locale'):
|
||||||
attr = getattr(queue, field, None)
|
attr = getattr(queue, field, None)
|
||||||
if callable(attr):
|
if callable(attr):
|
||||||
context['queue'][field] = attr()
|
context['queue'][field] = attr()
|
||||||
else:
|
else:
|
||||||
context['queue'][field] = attr
|
context['queue'][field] = attr
|
||||||
|
|
||||||
for field in ( 'title', 'created', 'modified', 'submitter_email',
|
for field in ('title', 'created', 'modified', 'submitter_email',
|
||||||
'status', 'get_status_display', 'on_hold', 'description',
|
'status', 'get_status_display', 'on_hold', 'description',
|
||||||
'resolution', 'priority', 'get_priority_display',
|
'resolution', 'priority', 'get_priority_display',
|
||||||
'last_escalation', 'ticket', 'ticket_for_url',
|
'last_escalation', 'ticket', 'ticket_for_url',
|
||||||
'get_status', 'ticket_url', 'staff_url', '_get_assigned_to'
|
'get_status', 'ticket_url', 'staff_url', '_get_assigned_to'
|
||||||
):
|
):
|
||||||
attr = getattr(ticket, field, None)
|
attr = getattr(ticket, field, None)
|
||||||
if callable(attr):
|
if callable(attr):
|
||||||
context['ticket'][field] = '%s' % attr()
|
context['ticket'][field] = '%s' % attr()
|
||||||
@ -277,10 +285,10 @@ def text_is_spam(text, request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if hasattr(settings, 'TYPEPAD_ANTISPAM_API_KEY'):
|
if hasattr(settings, 'TYPEPAD_ANTISPAM_API_KEY'):
|
||||||
ak.setAPIKey(key = settings.TYPEPAD_ANTISPAM_API_KEY)
|
ak.setAPIKey(key=settings.TYPEPAD_ANTISPAM_API_KEY)
|
||||||
ak.baseurl = 'api.antispam.typepad.com/1.1/'
|
ak.baseurl = 'api.antispam.typepad.com/1.1/'
|
||||||
elif hasattr(settings, 'AKISMET_API_KEY'):
|
elif hasattr(settings, 'AKISMET_API_KEY'):
|
||||||
ak.setAPIKey(key = settings.AKISMET_API_KEY)
|
ak.setAPIKey(key=settings.AKISMET_API_KEY)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -4,14 +4,15 @@
|
|||||||
#
|
#
|
||||||
# Translators:
|
# Translators:
|
||||||
# Translators:
|
# Translators:
|
||||||
|
# Morteza Nekoei <cisco.ir@gmail.com>, 2016
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: django-helpdesk\n"
|
"Project-Id-Version: django-helpdesk\n"
|
||||||
"Report-Msgid-Bugs-To: http://github.com/RossP/django-helpdesk/issues\n"
|
"Report-Msgid-Bugs-To: http://github.com/RossP/django-helpdesk/issues\n"
|
||||||
"POT-Creation-Date: 2014-07-26 14:14+0200\n"
|
"POT-Creation-Date: 2014-07-26 14:14+0200\n"
|
||||||
"PO-Revision-Date: 2014-08-01 09:58+0000\n"
|
"PO-Revision-Date: 2016-10-10 22:42+0000\n"
|
||||||
"Last-Translator: Ross Poulton <ross@rossp.org>\n"
|
"Last-Translator: Morteza Nekoei <cisco.ir@gmail.com>\n"
|
||||||
"Language-Team: Persian (Iran) (http://www.transifex.com/projects/p/django-helpdesk/language/fa_IR/)\n"
|
"Language-Team: Persian (Iran) (http://www.transifex.com/rossp/django-helpdesk/language/fa_IR/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
@ -27,35 +28,35 @@ msgstr ""
|
|||||||
#: templates/helpdesk/ticket_list.html:225 views/staff.py:1032
|
#: templates/helpdesk/ticket_list.html:225 views/staff.py:1032
|
||||||
#: views/staff.py:1038 views/staff.py:1044 views/staff.py:1050
|
#: views/staff.py:1038 views/staff.py:1044 views/staff.py:1050
|
||||||
msgid "Queue"
|
msgid "Queue"
|
||||||
msgstr ""
|
msgstr "صف"
|
||||||
|
|
||||||
#: forms.py:137
|
#: forms.py:137
|
||||||
msgid "Summary of the problem"
|
msgid "Summary of the problem"
|
||||||
msgstr ""
|
msgstr "خلاصه مشکل"
|
||||||
|
|
||||||
#: forms.py:142
|
#: forms.py:142
|
||||||
msgid "Submitter E-Mail Address"
|
msgid "Submitter E-Mail Address"
|
||||||
msgstr ""
|
msgstr "آدرس ایمیل ثبت کننده"
|
||||||
|
|
||||||
#: forms.py:144
|
#: forms.py:144
|
||||||
msgid ""
|
msgid ""
|
||||||
"This e-mail address will receive copies of all public updates to this "
|
"This e-mail address will receive copies of all public updates to this "
|
||||||
"ticket."
|
"ticket."
|
||||||
msgstr ""
|
msgstr "این آدرس ایمیل یک نسخه کپی از کلیهی بهروزرسانیهای عمومی این تیکت را دریافت خواهد کرد."
|
||||||
|
|
||||||
#: forms.py:150
|
#: forms.py:150
|
||||||
msgid "Description of Issue"
|
msgid "Description of Issue"
|
||||||
msgstr ""
|
msgstr "توضیح این مسئله"
|
||||||
|
|
||||||
#: forms.py:157
|
#: forms.py:157
|
||||||
msgid "Case owner"
|
msgid "Case owner"
|
||||||
msgstr ""
|
msgstr "مالک"
|
||||||
|
|
||||||
#: forms.py:158
|
#: forms.py:158
|
||||||
msgid ""
|
msgid ""
|
||||||
"If you select an owner other than yourself, they'll be e-mailed details of "
|
"If you select an owner other than yourself, they'll be e-mailed details of "
|
||||||
"this ticket immediately."
|
"this ticket immediately."
|
||||||
msgstr ""
|
msgstr "اگر یک مالک غیر از خودتان را انتخاب نمایید، جزئیات این تیکت به صورت بلادرنگ برای آنها ارسال خواهد شد"
|
||||||
|
|
||||||
#: forms.py:166 models.py:327 management/commands/escalate_tickets.py:154
|
#: forms.py:166 models.py:327 management/commands/escalate_tickets.py:154
|
||||||
#: templates/helpdesk/public_view_ticket.html:23
|
#: templates/helpdesk/public_view_ticket.html:23
|
||||||
@ -63,28 +64,28 @@ msgstr ""
|
|||||||
#: templates/helpdesk/ticket_desc_table.html:47
|
#: templates/helpdesk/ticket_desc_table.html:47
|
||||||
#: templates/helpdesk/ticket_list.html:94 views/staff.py:429
|
#: templates/helpdesk/ticket_list.html:94 views/staff.py:429
|
||||||
msgid "Priority"
|
msgid "Priority"
|
||||||
msgstr ""
|
msgstr "اولویت"
|
||||||
|
|
||||||
#: forms.py:167
|
#: forms.py:167
|
||||||
msgid "Please select a priority carefully. If unsure, leave it as '3'."
|
msgid "Please select a priority carefully. If unsure, leave it as '3'."
|
||||||
msgstr ""
|
msgstr "لطفا با دقت یک اولویت را انتخاب نمایید. در صورت عدم اطمینان، آن را رها نمایید. (اولویت پیشفرض: ۳)"
|
||||||
|
|
||||||
#: forms.py:174 forms.py:365 models.py:335 templates/helpdesk/ticket.html:186
|
#: forms.py:174 forms.py:365 models.py:335 templates/helpdesk/ticket.html:186
|
||||||
#: views/staff.py:439
|
#: views/staff.py:439
|
||||||
msgid "Due on"
|
msgid "Due on"
|
||||||
msgstr ""
|
msgstr "در مدت زمان"
|
||||||
|
|
||||||
#: forms.py:186 forms.py:370
|
#: forms.py:186 forms.py:370
|
||||||
msgid "Attach File"
|
msgid "Attach File"
|
||||||
msgstr ""
|
msgstr "ضمیمه کردن فایل"
|
||||||
|
|
||||||
#: forms.py:187 forms.py:371
|
#: forms.py:187 forms.py:371
|
||||||
msgid "You can attach a file such as a document or screenshot to this ticket."
|
msgid "You can attach a file such as a document or screenshot to this ticket."
|
||||||
msgstr ""
|
msgstr "شما میتوانید یک فایل مانند مستند یا عکس به این تیکت ضمیمه نمایید."
|
||||||
|
|
||||||
#: forms.py:240
|
#: forms.py:240
|
||||||
msgid "Ticket Opened"
|
msgid "Ticket Opened"
|
||||||
msgstr ""
|
msgstr "تیکت باز است"
|
||||||
|
|
||||||
#: forms.py:247
|
#: forms.py:247
|
||||||
#, python-format
|
#, python-format
|
||||||
@ -93,68 +94,68 @@ msgstr ""
|
|||||||
|
|
||||||
#: forms.py:337
|
#: forms.py:337
|
||||||
msgid "Summary of your query"
|
msgid "Summary of your query"
|
||||||
msgstr ""
|
msgstr "خلاصه پرس و جوی شما"
|
||||||
|
|
||||||
#: forms.py:342
|
#: forms.py:342
|
||||||
msgid "Your E-Mail Address"
|
msgid "Your E-Mail Address"
|
||||||
msgstr ""
|
msgstr "آدرس ایمیل شما"
|
||||||
|
|
||||||
#: forms.py:343
|
#: forms.py:343
|
||||||
msgid "We will e-mail you when your ticket is updated."
|
msgid "We will e-mail you when your ticket is updated."
|
||||||
msgstr ""
|
msgstr "زمانی که تیکت بهروزرسانی شود، برای شما ایمیلی ارسال خواهد شد."
|
||||||
|
|
||||||
#: forms.py:348
|
#: forms.py:348
|
||||||
msgid "Description of your issue"
|
msgid "Description of your issue"
|
||||||
msgstr ""
|
msgstr "توضیح مشکل شما"
|
||||||
|
|
||||||
#: forms.py:350
|
#: forms.py:350
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please be as descriptive as possible, including any details we may need to "
|
"Please be as descriptive as possible, including any details we may need to "
|
||||||
"address your query."
|
"address your query."
|
||||||
msgstr ""
|
msgstr "لطفا تا جای امکان توضیحات لازم را وارد نمایید، توضیحاتی شامل هر نوع جزئیاتی که ما برای یافتن پرس و جوی شما لازم داریم."
|
||||||
|
|
||||||
#: forms.py:358
|
#: forms.py:358
|
||||||
msgid "Urgency"
|
msgid "Urgency"
|
||||||
msgstr ""
|
msgstr "اورژانسی"
|
||||||
|
|
||||||
#: forms.py:359
|
#: forms.py:359
|
||||||
msgid "Please select a priority carefully."
|
msgid "Please select a priority carefully."
|
||||||
msgstr ""
|
msgstr "لطفا با دقت یک اولویت را انتخاب نمایید."
|
||||||
|
|
||||||
#: forms.py:419
|
#: forms.py:419
|
||||||
msgid "Ticket Opened Via Web"
|
msgid "Ticket Opened Via Web"
|
||||||
msgstr ""
|
msgstr "تیکت از طریق وب باز شده است."
|
||||||
|
|
||||||
#: forms.py:486
|
#: forms.py:486
|
||||||
msgid "Show Ticket List on Login?"
|
msgid "Show Ticket List on Login?"
|
||||||
msgstr ""
|
msgstr "لیست تیکتها در زمان ورود نمایش داده شود؟"
|
||||||
|
|
||||||
#: forms.py:487
|
#: forms.py:487
|
||||||
msgid "Display the ticket list upon login? Otherwise, the dashboard is shown."
|
msgid "Display the ticket list upon login? Otherwise, the dashboard is shown."
|
||||||
msgstr ""
|
msgstr "لیست تیکتها در زمان ورود نمایش داده شود؟ در غیر اینصورت در داشبورد نمایش داده خواهند شد."
|
||||||
|
|
||||||
#: forms.py:492
|
#: forms.py:492
|
||||||
msgid "E-mail me on ticket change?"
|
msgid "E-mail me on ticket change?"
|
||||||
msgstr ""
|
msgstr "تغییرات تیکت به من ایمیل شود؟"
|
||||||
|
|
||||||
#: forms.py:493
|
#: forms.py:493
|
||||||
msgid ""
|
msgid ""
|
||||||
"If you're the ticket owner and the ticket is changed via the web by somebody"
|
"If you're the ticket owner and the ticket is changed via the web by somebody"
|
||||||
" else, do you want to receive an e-mail?"
|
" else, do you want to receive an e-mail?"
|
||||||
msgstr ""
|
msgstr "اگر شما مالک تیکت هستید و تیکت از طریق وب توسط افراد دیگر تغییر یافت، آیا مایل هستید یک ایمیل اطلاع رسانی دریافت نمایید؟"
|
||||||
|
|
||||||
#: forms.py:498
|
#: forms.py:498
|
||||||
msgid "E-mail me when assigned a ticket?"
|
msgid "E-mail me when assigned a ticket?"
|
||||||
msgstr ""
|
msgstr "زمانیکه یک تیکت تخصیص یافت به من ایمیل ارسال شود؟"
|
||||||
|
|
||||||
#: forms.py:499
|
#: forms.py:499
|
||||||
msgid ""
|
msgid ""
|
||||||
"If you are assigned a ticket via the web, do you want to receive an e-mail?"
|
"If you are assigned a ticket via the web, do you want to receive an e-mail?"
|
||||||
msgstr ""
|
msgstr "اگر شما یک تیکت را از طریق وب تخصیص دادید، آیا مایل هستید یک ایمیل اطلاعرسانی دریافت نمایید؟"
|
||||||
|
|
||||||
#: forms.py:504
|
#: forms.py:504
|
||||||
msgid "E-mail me when a ticket is changed via the API?"
|
msgid "E-mail me when a ticket is changed via the API?"
|
||||||
msgstr ""
|
msgstr "زمانیکه یک تیکت از طریق API تغییر یافت، برای من ایمیل ارسال شود؟"
|
||||||
|
|
||||||
#: forms.py:505
|
#: forms.py:505
|
||||||
msgid "If a ticket is altered by the API, do you want to receive an e-mail?"
|
msgid "If a ticket is altered by the API, do you want to receive an e-mail?"
|
||||||
@ -162,22 +163,22 @@ msgstr ""
|
|||||||
|
|
||||||
#: forms.py:510
|
#: forms.py:510
|
||||||
msgid "Number of tickets to show per page"
|
msgid "Number of tickets to show per page"
|
||||||
msgstr ""
|
msgstr "تعداد تیکتهای قابل نمایش در هر صفحه"
|
||||||
|
|
||||||
#: forms.py:511
|
#: forms.py:511
|
||||||
msgid "How many tickets do you want to see on the Ticket List page?"
|
msgid "How many tickets do you want to see on the Ticket List page?"
|
||||||
msgstr ""
|
msgstr "مایل هستید چه تعداد تیکت در صفحهی لیست تیکت برای شما نمایش داده شود؟"
|
||||||
|
|
||||||
#: forms.py:518
|
#: forms.py:518
|
||||||
msgid "Use my e-mail address when submitting tickets?"
|
msgid "Use my e-mail address when submitting tickets?"
|
||||||
msgstr ""
|
msgstr "ایمیل آدرس من در زمان ثبت تیکتها استفاده شود."
|
||||||
|
|
||||||
#: forms.py:519
|
#: forms.py:519
|
||||||
msgid ""
|
msgid ""
|
||||||
"When you submit a ticket, do you want to automatically use your e-mail "
|
"When you submit a ticket, do you want to automatically use your e-mail "
|
||||||
"address as the submitter address? You can type a different e-mail address "
|
"address as the submitter address? You can type a different e-mail address "
|
||||||
"when entering the ticket if needed, this option only changes the default."
|
"when entering the ticket if needed, this option only changes the default."
|
||||||
msgstr ""
|
msgstr "زمانی که یک تیکت ثبت نمودید، آیا مایل هستید به صورت خودکار آدرس ایمیل خود را به عنوان آدرس ایمیل ثبت کننده استفاده نمایید؟ شما میتوانید در صورت تمایل یک آدرس ایمیل دیگر زمان ثبت تیکت استفاده نمایید."
|
||||||
|
|
||||||
#: models.py:35 models.py:261 models.py:503 models.py:817 models.py:853
|
#: models.py:35 models.py:261 models.py:503 models.py:817 models.py:853
|
||||||
#: templates/helpdesk/dashboard.html:58 templates/helpdesk/dashboard.html:78
|
#: templates/helpdesk/dashboard.html:58 templates/helpdesk/dashboard.html:78
|
||||||
@ -185,7 +186,7 @@ msgstr ""
|
|||||||
#: templates/helpdesk/ticket.html:178 templates/helpdesk/ticket_list.html:85
|
#: templates/helpdesk/ticket.html:178 templates/helpdesk/ticket_list.html:85
|
||||||
#: templates/helpdesk/ticket_list.html:225 views/staff.py:419
|
#: templates/helpdesk/ticket_list.html:225 views/staff.py:419
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr ""
|
msgstr "عنوان"
|
||||||
|
|
||||||
#: models.py:40 models.py:822 models.py:1206
|
#: models.py:40 models.py:822 models.py:1206
|
||||||
msgid "Slug"
|
msgid "Slug"
|
||||||
@ -201,39 +202,39 @@ msgstr ""
|
|||||||
#: templates/helpdesk/email_ignore_list.html:13
|
#: templates/helpdesk/email_ignore_list.html:13
|
||||||
#: templates/helpdesk/ticket_cc_list.html:15
|
#: templates/helpdesk/ticket_cc_list.html:15
|
||||||
msgid "E-Mail Address"
|
msgid "E-Mail Address"
|
||||||
msgstr ""
|
msgstr "آدرس ایمیل"
|
||||||
|
|
||||||
#: models.py:49
|
#: models.py:49
|
||||||
msgid ""
|
msgid ""
|
||||||
"All outgoing e-mails for this queue will use this e-mail address. If you use"
|
"All outgoing e-mails for this queue will use this e-mail address. If you use"
|
||||||
" IMAP or POP3, this should be the e-mail address for that mailbox."
|
" IMAP or POP3, this should be the e-mail address for that mailbox."
|
||||||
msgstr ""
|
msgstr "کلیهی ایمیلهای ارسالی برای این صف، از این ایمیل استفاده خواهند کرد. اگر میخواهید از IMAP یا POP3 استفاده کنید، آدرس ایمیل شما باید مرتبط با آن ایمیل سرور باشد."
|
||||||
|
|
||||||
#: models.py:55 models.py:794
|
#: models.py:55 models.py:794
|
||||||
msgid "Locale"
|
msgid "Locale"
|
||||||
msgstr ""
|
msgstr "موقعیت/محل"
|
||||||
|
|
||||||
#: models.py:59
|
#: models.py:59
|
||||||
msgid ""
|
msgid ""
|
||||||
"Locale of this queue. All correspondence in this queue will be in this "
|
"Locale of this queue. All correspondence in this queue will be in this "
|
||||||
"language."
|
"language."
|
||||||
msgstr ""
|
msgstr "موقعیت این صف. کلیهی پاسخها و تعاملات در این صف، با این زبان استفاده خواهد شد."
|
||||||
|
|
||||||
#: models.py:63
|
#: models.py:63
|
||||||
msgid "Allow Public Submission?"
|
msgid "Allow Public Submission?"
|
||||||
msgstr ""
|
msgstr "اجازه دادن ثبتهای عمومی؟"
|
||||||
|
|
||||||
#: models.py:66
|
#: models.py:66
|
||||||
msgid "Should this queue be listed on the public submission form?"
|
msgid "Should this queue be listed on the public submission form?"
|
||||||
msgstr ""
|
msgstr "آیا این صف در فرم ثبتهای عمومی لیست شود؟"
|
||||||
|
|
||||||
#: models.py:71
|
#: models.py:71
|
||||||
msgid "Allow E-Mail Submission?"
|
msgid "Allow E-Mail Submission?"
|
||||||
msgstr ""
|
msgstr "اجازه دادن ثبت ایمیل؟"
|
||||||
|
|
||||||
#: models.py:74
|
#: models.py:74
|
||||||
msgid "Do you want to poll the e-mail box below for new tickets?"
|
msgid "Do you want to poll the e-mail box below for new tickets?"
|
||||||
msgstr ""
|
msgstr "آیا میخواهید ایمیل زیر را برای تیکتهای جدید انتخاب نمایید؟"
|
||||||
|
|
||||||
#: models.py:79
|
#: models.py:79
|
||||||
msgid "Escalation Days"
|
msgid "Escalation Days"
|
||||||
@ -243,22 +244,22 @@ msgstr ""
|
|||||||
msgid ""
|
msgid ""
|
||||||
"For tickets which are not held, how often do you wish to increase their "
|
"For tickets which are not held, how often do you wish to increase their "
|
||||||
"priority? Set to 0 for no escalation."
|
"priority? Set to 0 for no escalation."
|
||||||
msgstr ""
|
msgstr "برای تیکتهایی که نگهداری نشدهاند، آیا میخواهید اولویتی برای آنها در نظر بگیرید؟ عدد ۰ را برای عدم استفاده از اولویت بکار برید."
|
||||||
|
|
||||||
#: models.py:87
|
#: models.py:87
|
||||||
msgid "New Ticket CC Address"
|
msgid "New Ticket CC Address"
|
||||||
msgstr ""
|
msgstr "یک آدرس کپی (CC) برای تیکت جدید"
|
||||||
|
|
||||||
#: models.py:91
|
#: models.py:91
|
||||||
msgid ""
|
msgid ""
|
||||||
"If an e-mail address is entered here, then it will receive notification of "
|
"If an e-mail address is entered here, then it will receive notification of "
|
||||||
"all new tickets created for this queue. Enter a comma between multiple "
|
"all new tickets created for this queue. Enter a comma between multiple "
|
||||||
"e-mail addresses."
|
"e-mail addresses."
|
||||||
msgstr ""
|
msgstr "اگر یک آدرس ایمیل در این قسمت وارد شود، این آدرس کلیهی تیکتهای ایجاد شده برای این صف را دریافت خواهد نمود. جهت استفاده از بیش از یک آدرس ایمیل از کاما استفاده نمایید."
|
||||||
|
|
||||||
#: models.py:97
|
#: models.py:97
|
||||||
msgid "Updated Ticket CC Address"
|
msgid "Updated Ticket CC Address"
|
||||||
msgstr ""
|
msgstr "آدرس تیکت (CC) بهروزرسانی شده"
|
||||||
|
|
||||||
#: models.py:101
|
#: models.py:101
|
||||||
msgid ""
|
msgid ""
|
||||||
|
Binary file not shown.
@ -6,18 +6,20 @@
|
|||||||
# Translators:
|
# Translators:
|
||||||
# Alexandre Papin <papin.alexandre@me.com>, 2013
|
# Alexandre Papin <papin.alexandre@me.com>, 2013
|
||||||
# Alex Garel <alex.garel@gmail.com>, 2012
|
# Alex Garel <alex.garel@gmail.com>, 2012
|
||||||
# Antoine Nguyen <tonio@ngyn.org>, 2014
|
# Antoine Nguyen <tonio@ngyn.org>, 2014,2016
|
||||||
|
# Etienne Desgagné <etienne.desgagne@evimbec.ca>, 2015
|
||||||
# yaap <gracieux.guillaume@gmail.com>, 2012
|
# yaap <gracieux.guillaume@gmail.com>, 2012
|
||||||
# kolin22 <kolin22@gmail.com>, 2011
|
# kolin22 <inactive+kolin22@transifex.com>, 2011
|
||||||
# Ross Poulton <ross@rossp.org>, 2011
|
# Paul Guichon <fsx999@gmail.com>, 2015
|
||||||
|
# Ross Poulton <ross@rossp.org>, 2011,2015
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: django-helpdesk\n"
|
"Project-Id-Version: django-helpdesk\n"
|
||||||
"Report-Msgid-Bugs-To: http://github.com/RossP/django-helpdesk/issues\n"
|
"Report-Msgid-Bugs-To: http://github.com/RossP/django-helpdesk/issues\n"
|
||||||
"POT-Creation-Date: 2014-07-26 14:14+0200\n"
|
"POT-Creation-Date: 2014-07-26 14:14+0200\n"
|
||||||
"PO-Revision-Date: 2014-09-17 07:12+0000\n"
|
"PO-Revision-Date: 2016-06-07 12:22+0000\n"
|
||||||
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
|
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
|
||||||
"Language-Team: French (http://www.transifex.com/projects/p/django-helpdesk/language/fr/)\n"
|
"Language-Team: French (http://www.transifex.com/rossp/django-helpdesk/language/fr/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
@ -497,7 +499,7 @@ msgstr " - En attente"
|
|||||||
|
|
||||||
#: models.py:394
|
#: models.py:394
|
||||||
msgid " - Open dependencies"
|
msgid " - Open dependencies"
|
||||||
msgstr ""
|
msgstr "Dépendance ouverte"
|
||||||
|
|
||||||
#: models.py:448 models.py:494 models.py:1117 models.py:1280 models.py:1309
|
#: models.py:448 models.py:494 models.py:1117 models.py:1280 models.py:1309
|
||||||
#: templates/helpdesk/public_homepage.html:78
|
#: templates/helpdesk/public_homepage.html:78
|
||||||
@ -548,7 +550,7 @@ msgstr "Suivi"
|
|||||||
|
|
||||||
#: models.py:543
|
#: models.py:543
|
||||||
msgid "Follow-ups"
|
msgid "Follow-ups"
|
||||||
msgstr ""
|
msgstr "Suivis"
|
||||||
|
|
||||||
#: models.py:570 models.py:1285
|
#: models.py:570 models.py:1285
|
||||||
msgid "Field"
|
msgid "Field"
|
||||||
@ -578,11 +580,11 @@ msgstr "changé de \"%(old_value)s\" à \"%(new_value)s\""
|
|||||||
|
|
||||||
#: models.py:600
|
#: models.py:600
|
||||||
msgid "Ticket change"
|
msgid "Ticket change"
|
||||||
msgstr ""
|
msgstr "Changement de ticket"
|
||||||
|
|
||||||
#: models.py:601
|
#: models.py:601
|
||||||
msgid "Ticket changes"
|
msgid "Ticket changes"
|
||||||
msgstr ""
|
msgstr "Changements de ticket"
|
||||||
|
|
||||||
#: models.py:632
|
#: models.py:632
|
||||||
msgid "File"
|
msgid "File"
|
||||||
@ -640,11 +642,11 @@ msgstr "Context disponible: {{ ticket }} - objet du ticket (eg {{ ticket.title }
|
|||||||
|
|
||||||
#: models.py:705
|
#: models.py:705
|
||||||
msgid "Pre-set reply"
|
msgid "Pre-set reply"
|
||||||
msgstr ""
|
msgstr "Réponse préétablie"
|
||||||
|
|
||||||
#: models.py:706
|
#: models.py:706
|
||||||
msgid "Pre-set replies"
|
msgid "Pre-set replies"
|
||||||
msgstr ""
|
msgstr "Réponse préétablie"
|
||||||
|
|
||||||
#: models.py:727
|
#: models.py:727
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -658,11 +660,11 @@ msgstr "Jours exclus du processus d'augmentation des priorités"
|
|||||||
|
|
||||||
#: models.py:746
|
#: models.py:746
|
||||||
msgid "Escalation exclusion"
|
msgid "Escalation exclusion"
|
||||||
msgstr ""
|
msgstr "Exclusion priorités"
|
||||||
|
|
||||||
#: models.py:747
|
#: models.py:747
|
||||||
msgid "Escalation exclusions"
|
msgid "Escalation exclusions"
|
||||||
msgstr ""
|
msgstr "Exclusion priorités"
|
||||||
|
|
||||||
#: models.py:760
|
#: models.py:760
|
||||||
msgid "Template Name"
|
msgid "Template Name"
|
||||||
@ -713,19 +715,19 @@ msgstr "Langue de ce modèle."
|
|||||||
|
|
||||||
#: models.py:806
|
#: models.py:806
|
||||||
msgid "e-mail template"
|
msgid "e-mail template"
|
||||||
msgstr ""
|
msgstr "Modèle d'e-mail"
|
||||||
|
|
||||||
#: models.py:807
|
#: models.py:807
|
||||||
msgid "e-mail templates"
|
msgid "e-mail templates"
|
||||||
msgstr ""
|
msgstr "Modèles d'e-mail"
|
||||||
|
|
||||||
#: models.py:834
|
#: models.py:834
|
||||||
msgid "Knowledge base category"
|
msgid "Knowledge base category"
|
||||||
msgstr ""
|
msgstr "Catégorie de la base de connaissance"
|
||||||
|
|
||||||
#: models.py:835
|
#: models.py:835
|
||||||
msgid "Knowledge base categories"
|
msgid "Knowledge base categories"
|
||||||
msgstr ""
|
msgstr "Catégories de la base de connaissance"
|
||||||
|
|
||||||
#: models.py:849 templates/helpdesk/kb_index.html:11
|
#: models.py:849 templates/helpdesk/kb_index.html:11
|
||||||
#: templates/helpdesk/public_homepage.html:11
|
#: templates/helpdesk/public_homepage.html:11
|
||||||
@ -770,11 +772,11 @@ msgstr "Non évalué"
|
|||||||
|
|
||||||
#: models.py:901
|
#: models.py:901
|
||||||
msgid "Knowledge base item"
|
msgid "Knowledge base item"
|
||||||
msgstr ""
|
msgstr "Élément de la base de connaissance"
|
||||||
|
|
||||||
#: models.py:902
|
#: models.py:902
|
||||||
msgid "Knowledge base items"
|
msgid "Knowledge base items"
|
||||||
msgstr ""
|
msgstr "Éléments de la base de connaissance"
|
||||||
|
|
||||||
#: models.py:926 templates/helpdesk/ticket_list.html:170
|
#: models.py:926 templates/helpdesk/ticket_list.html:170
|
||||||
msgid "Query Name"
|
msgid "Query Name"
|
||||||
@ -855,11 +857,11 @@ msgstr "Voulez-vous enregistrer les courriels provenant de cette adresse dans la
|
|||||||
|
|
||||||
#: models.py:1101
|
#: models.py:1101
|
||||||
msgid "Ignored e-mail address"
|
msgid "Ignored e-mail address"
|
||||||
msgstr ""
|
msgstr "Adresse e-mail ignorée"
|
||||||
|
|
||||||
#: models.py:1102
|
#: models.py:1102
|
||||||
msgid "Ignored e-mail addresses"
|
msgid "Ignored e-mail addresses"
|
||||||
msgstr ""
|
msgstr "Adresses e-mail ignorées"
|
||||||
|
|
||||||
#: models.py:1124
|
#: models.py:1124
|
||||||
msgid "User who wishes to receive updates for this ticket."
|
msgid "User who wishes to receive updates for this ticket."
|
||||||
@ -1024,11 +1026,11 @@ msgstr "Champs personnalisés"
|
|||||||
|
|
||||||
#: models.py:1297
|
#: models.py:1297
|
||||||
msgid "Ticket custom field value"
|
msgid "Ticket custom field value"
|
||||||
msgstr ""
|
msgstr "Valeur champs personnalisé billet"
|
||||||
|
|
||||||
#: models.py:1298
|
#: models.py:1298
|
||||||
msgid "Ticket custom field values"
|
msgid "Ticket custom field values"
|
||||||
msgstr ""
|
msgstr "Valeur champs personnalisé billet"
|
||||||
|
|
||||||
#: models.py:1315
|
#: models.py:1315
|
||||||
msgid "Depends On Ticket"
|
msgid "Depends On Ticket"
|
||||||
@ -1036,11 +1038,11 @@ msgstr "Dépend du ticket"
|
|||||||
|
|
||||||
#: models.py:1324
|
#: models.py:1324
|
||||||
msgid "Ticket dependency"
|
msgid "Ticket dependency"
|
||||||
msgstr ""
|
msgstr "Dépendance du ticket"
|
||||||
|
|
||||||
#: models.py:1325
|
#: models.py:1325
|
||||||
msgid "Ticket dependencies"
|
msgid "Ticket dependencies"
|
||||||
msgstr ""
|
msgstr "Dépendances du ticket"
|
||||||
|
|
||||||
#: management/commands/create_usersettings.py:25
|
#: management/commands/create_usersettings.py:25
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -1093,7 +1095,7 @@ msgstr "(Mis à jour)"
|
|||||||
#: templates/helpdesk/attribution.html:2
|
#: templates/helpdesk/attribution.html:2
|
||||||
msgid ""
|
msgid ""
|
||||||
"<a href='https://github.com/rossp/django-helpdesk'>django-helpdesk</a>."
|
"<a href='https://github.com/rossp/django-helpdesk'>django-helpdesk</a>."
|
||||||
msgstr ""
|
msgstr "<a href='https://github.com/rossp/django-helpdesk'>django-helpdesk</a>."
|
||||||
|
|
||||||
#: templates/helpdesk/base.html:10
|
#: templates/helpdesk/base.html:10
|
||||||
msgid "Powered by django-helpdesk"
|
msgid "Powered by django-helpdesk"
|
||||||
@ -1144,7 +1146,7 @@ msgstr "Supprimer la requête enregistrée"
|
|||||||
|
|
||||||
#: templates/helpdesk/confirm_delete_saved_query.html:6
|
#: templates/helpdesk/confirm_delete_saved_query.html:6
|
||||||
msgid "Delete Query"
|
msgid "Delete Query"
|
||||||
msgstr ""
|
msgstr "Supprimer la requête"
|
||||||
|
|
||||||
#: templates/helpdesk/confirm_delete_saved_query.html:8
|
#: templates/helpdesk/confirm_delete_saved_query.html:8
|
||||||
#, python-format
|
#, python-format
|
||||||
@ -1152,13 +1154,13 @@ msgid ""
|
|||||||
"Are you sure you want to delete this saved filter "
|
"Are you sure you want to delete this saved filter "
|
||||||
"(<em>%(query_title)s</em>)? To re-create it, you will need to manually re-"
|
"(<em>%(query_title)s</em>)? To re-create it, you will need to manually re-"
|
||||||
"filter your ticket listing."
|
"filter your ticket listing."
|
||||||
msgstr ""
|
msgstr "Êtes vous certain de vouloir supprimer ce filtre enregistré (<em>%(query_title)s</em>)? Pour le recréer, vous devrez refiltrer la liste de ticket manuellement."
|
||||||
|
|
||||||
#: templates/helpdesk/confirm_delete_saved_query.html:11
|
#: templates/helpdesk/confirm_delete_saved_query.html:11
|
||||||
msgid ""
|
msgid ""
|
||||||
"You have shared this query, so other users may be using it. If you delete "
|
"You have shared this query, so other users may be using it. If you delete "
|
||||||
"it, they will have to manually create their own query."
|
"it, they will have to manually create their own query."
|
||||||
msgstr ""
|
msgstr "Vous avez partagé cette requête, il est donc possible que d'autres l'utilisent. Si vous la supprimez, il devront créer la leur manuellement."
|
||||||
|
|
||||||
#: templates/helpdesk/confirm_delete_saved_query.html:14
|
#: templates/helpdesk/confirm_delete_saved_query.html:14
|
||||||
#: templates/helpdesk/delete_ticket.html:10
|
#: templates/helpdesk/delete_ticket.html:10
|
||||||
@ -1183,13 +1185,13 @@ msgstr "Soumettre un Ticket"
|
|||||||
#: templates/helpdesk/create_ticket.html:11
|
#: templates/helpdesk/create_ticket.html:11
|
||||||
#: templates/helpdesk/edit_ticket.html:11
|
#: templates/helpdesk/edit_ticket.html:11
|
||||||
msgid "Unless otherwise stated, all fields are required."
|
msgid "Unless otherwise stated, all fields are required."
|
||||||
msgstr ""
|
msgstr "Sauf mention contraire, tous les champs sont requis."
|
||||||
|
|
||||||
#: templates/helpdesk/create_ticket.html:11
|
#: templates/helpdesk/create_ticket.html:11
|
||||||
#: templates/helpdesk/edit_ticket.html:11
|
#: templates/helpdesk/edit_ticket.html:11
|
||||||
#: templates/helpdesk/public_homepage.html:28
|
#: templates/helpdesk/public_homepage.html:28
|
||||||
msgid "Please provide as descriptive a title and description as possible."
|
msgid "Please provide as descriptive a title and description as possible."
|
||||||
msgstr ""
|
msgstr "Veuillez fournir un titre et une description aussi détaillés que possible."
|
||||||
|
|
||||||
#: templates/helpdesk/create_ticket.html:30
|
#: templates/helpdesk/create_ticket.html:30
|
||||||
#: templates/helpdesk/public_homepage.html:55
|
#: templates/helpdesk/public_homepage.html:55
|
||||||
@ -1205,7 +1207,7 @@ msgid ""
|
|||||||
"Welcome to your Helpdesk Dashboard! From here you can quickly see tickets "
|
"Welcome to your Helpdesk Dashboard! From here you can quickly see tickets "
|
||||||
"submitted by you, tickets you are working on, and those tickets that have no"
|
"submitted by you, tickets you are working on, and those tickets that have no"
|
||||||
" owner."
|
" owner."
|
||||||
msgstr "Bienvenue dans votre tableau de bord! D'ici vous pouvez rapidement voir les tickets que vous avez soumis, ceux sur lesquels vous travaillez et ceux sur qui n'ont pas de propriétaire."
|
msgstr "Bienvenue dans votre tableau de bord! D'ici vous pouvez rapidement voir les tickets que vous avez soumis, ceux sur lesquels vous travaillez et ceux qui n'ont pas de propriétaire."
|
||||||
|
|
||||||
#: templates/helpdesk/dashboard.html:14
|
#: templates/helpdesk/dashboard.html:14
|
||||||
msgid "Helpdesk Summary"
|
msgid "Helpdesk Summary"
|
||||||
@ -1213,37 +1215,37 @@ msgstr "Résumé Helpdesk"
|
|||||||
|
|
||||||
#: templates/helpdesk/dashboard.html:36
|
#: templates/helpdesk/dashboard.html:36
|
||||||
msgid "Current Ticket Stats"
|
msgid "Current Ticket Stats"
|
||||||
msgstr ""
|
msgstr "Statistiques actuelles des tickets"
|
||||||
|
|
||||||
#: templates/helpdesk/dashboard.html:37
|
#: templates/helpdesk/dashboard.html:37
|
||||||
msgid "Average number of days until ticket is closed (all tickets): "
|
msgid "Average number of days until ticket is closed (all tickets): "
|
||||||
msgstr ""
|
msgstr "Délai moyen de fermeture d'un ticket (tous tickets) :"
|
||||||
|
|
||||||
#: templates/helpdesk/dashboard.html:38
|
#: templates/helpdesk/dashboard.html:38
|
||||||
msgid ""
|
msgid ""
|
||||||
"Average number of days until ticket is closed (tickets opened in last 60 "
|
"Average number of days until ticket is closed (tickets opened in last 60 "
|
||||||
"days): "
|
"days): "
|
||||||
msgstr ""
|
msgstr "Délai moyen de fermeture d'un ticket (tickets ouverts dans les 60 derniers jours) :"
|
||||||
|
|
||||||
#: templates/helpdesk/dashboard.html:39
|
#: templates/helpdesk/dashboard.html:39
|
||||||
msgid "Click"
|
msgid "Click"
|
||||||
msgstr ""
|
msgstr "Cliquer"
|
||||||
|
|
||||||
#: templates/helpdesk/dashboard.html:39
|
#: templates/helpdesk/dashboard.html:39
|
||||||
msgid "for detailed average by month."
|
msgid "for detailed average by month."
|
||||||
msgstr ""
|
msgstr "Pour la moyenne par mois détaillé"
|
||||||
|
|
||||||
#: templates/helpdesk/dashboard.html:40
|
#: templates/helpdesk/dashboard.html:40
|
||||||
msgid "Distribution of open tickets, grouped by time period:"
|
msgid "Distribution of open tickets, grouped by time period:"
|
||||||
msgstr ""
|
msgstr "Distribution des tickets ouverts, groupés par période temporelle :"
|
||||||
|
|
||||||
#: templates/helpdesk/dashboard.html:41
|
#: templates/helpdesk/dashboard.html:41
|
||||||
msgid "Days since opened"
|
msgid "Days since opened"
|
||||||
msgstr ""
|
msgstr "Jours passés depuis l'ouverture"
|
||||||
|
|
||||||
#: templates/helpdesk/dashboard.html:41
|
#: templates/helpdesk/dashboard.html:41
|
||||||
msgid "Number of open tickets"
|
msgid "Number of open tickets"
|
||||||
msgstr ""
|
msgstr "Nombre de tickets ouverts"
|
||||||
|
|
||||||
#: templates/helpdesk/dashboard.html:57
|
#: templates/helpdesk/dashboard.html:57
|
||||||
msgid "All Tickets submitted by you"
|
msgid "All Tickets submitted by you"
|
||||||
@ -1262,7 +1264,7 @@ msgstr "Dernière mise à jour"
|
|||||||
|
|
||||||
#: templates/helpdesk/dashboard.html:77
|
#: templates/helpdesk/dashboard.html:77
|
||||||
msgid "Open Tickets assigned to you (you are working on this ticket)"
|
msgid "Open Tickets assigned to you (you are working on this ticket)"
|
||||||
msgstr "Ouvrir les tickets qui vous sont assignés (vous travaillez sur ce ticket)"
|
msgstr "Tickets ouverts qui vous sont assignés (vous travaillez sur ce ticket)"
|
||||||
|
|
||||||
#: templates/helpdesk/dashboard.html:92
|
#: templates/helpdesk/dashboard.html:92
|
||||||
msgid "You have no tickets assigned to you."
|
msgid "You have no tickets assigned to you."
|
||||||
@ -1270,7 +1272,7 @@ msgstr "Vous n'avez aucun ticket qui vous est assigné."
|
|||||||
|
|
||||||
#: templates/helpdesk/dashboard.html:99
|
#: templates/helpdesk/dashboard.html:99
|
||||||
msgid "(pick up a ticket if you start to work on it)"
|
msgid "(pick up a ticket if you start to work on it)"
|
||||||
msgstr ""
|
msgstr "(assignez vous un ticket si vous commencez à travailler dessus)"
|
||||||
|
|
||||||
#: templates/helpdesk/dashboard.html:110
|
#: templates/helpdesk/dashboard.html:110
|
||||||
#: templates/helpdesk/ticket_desc_table.html:38
|
#: templates/helpdesk/ticket_desc_table.html:38
|
||||||
@ -1305,7 +1307,7 @@ msgid ""
|
|||||||
"Are you sure you want to delete this ticket (<em>%(ticket_title)s</em>)? All"
|
"Are you sure you want to delete this ticket (<em>%(ticket_title)s</em>)? All"
|
||||||
" traces of the ticket, including followups, attachments, and updates will be"
|
" traces of the ticket, including followups, attachments, and updates will be"
|
||||||
" irreversibly removed."
|
" irreversibly removed."
|
||||||
msgstr ""
|
msgstr "Êtes vous certain de vouloir supprimer ce ticket (<em>%(ticket_title)s</em>) ? Toutes les traces associées, à savoir les relances, les pièces jointes et les mises à jour seront irrémédiablement supprimés."
|
||||||
|
|
||||||
#: templates/helpdesk/edit_ticket.html:3
|
#: templates/helpdesk/edit_ticket.html:3
|
||||||
msgid "Edit Ticket"
|
msgid "Edit Ticket"
|
||||||
@ -1313,18 +1315,18 @@ msgstr "Editer le ticket"
|
|||||||
|
|
||||||
#: templates/helpdesk/edit_ticket.html:9
|
#: templates/helpdesk/edit_ticket.html:9
|
||||||
msgid "Edit a Ticket"
|
msgid "Edit a Ticket"
|
||||||
msgstr ""
|
msgstr "Modification du ticket"
|
||||||
|
|
||||||
#: templates/helpdesk/edit_ticket.html:13
|
#: templates/helpdesk/edit_ticket.html:13
|
||||||
msgid "Note"
|
msgid "Note"
|
||||||
msgstr ""
|
msgstr "Note"
|
||||||
|
|
||||||
#: templates/helpdesk/edit_ticket.html:13
|
#: templates/helpdesk/edit_ticket.html:13
|
||||||
msgid ""
|
msgid ""
|
||||||
"Editing a ticket does <em>not</em> send an e-mail to the ticket owner or "
|
"Editing a ticket does <em>not</em> send an e-mail to the ticket owner or "
|
||||||
"submitter. No new details should be entered, this form should only be used "
|
"submitter. No new details should be entered, this form should only be used "
|
||||||
"to fix incorrect details or clean up the submission."
|
"to fix incorrect details or clean up the submission."
|
||||||
msgstr ""
|
msgstr "Modifier un ticket <em>n'envoie pas</em> d'e-mail au propriétaire ou au rapporteur du ticket. Aucun détail ne doit être ajouté, ce formulaire doit juste être utilisé pour corriger des informations incorrectes ou nettoyer la soumission."
|
||||||
|
|
||||||
#: templates/helpdesk/edit_ticket.html:33
|
#: templates/helpdesk/edit_ticket.html:33
|
||||||
msgid "Save Changes"
|
msgid "Save Changes"
|
||||||
@ -1340,14 +1342,14 @@ msgstr "Ignorer l'adresse e-mail"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"To ignore an e-mail address and prevent any emails from that address "
|
"To ignore an e-mail address and prevent any emails from that address "
|
||||||
"creating tickets automatically, enter the e-mail address below."
|
"creating tickets automatically, enter the e-mail address below."
|
||||||
msgstr ""
|
msgstr "Pour prévenir la réception et ignoré les courriels envoyé de cette adresse. entrez le courriel de l’adresse ci-dessous pour la création automatique de billet"
|
||||||
|
|
||||||
#: templates/helpdesk/email_ignore_add.html:10
|
#: templates/helpdesk/email_ignore_add.html:10
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can either enter a whole e-mail address such as "
|
"You can either enter a whole e-mail address such as "
|
||||||
"<em>email@domain.com</em> or a portion of an e-mail address with a wildcard,"
|
"<em>email@domain.com</em> or a portion of an e-mail address with a wildcard,"
|
||||||
" such as <em>*@domain.com</em> or <em>user@*</em>."
|
" such as <em>*@domain.com</em> or <em>user@*</em>."
|
||||||
msgstr ""
|
msgstr "Vous pouvez inscrire une adresse de courriel complet tel que <em>email@domain.com</em> ou en partie avec « wildcard » Tel que <em>*@domain.com</em> or <em>user@*</em>"
|
||||||
|
|
||||||
#: templates/helpdesk/email_ignore_del.html:3
|
#: templates/helpdesk/email_ignore_del.html:3
|
||||||
msgid "Delete Ignored E-Mail Address"
|
msgid "Delete Ignored E-Mail Address"
|
||||||
@ -1355,7 +1357,7 @@ msgstr "Supprimer l'adresse e-mail ignorée"
|
|||||||
|
|
||||||
#: templates/helpdesk/email_ignore_del.html:6
|
#: templates/helpdesk/email_ignore_del.html:6
|
||||||
msgid "Un-Ignore E-Mail Address"
|
msgid "Un-Ignore E-Mail Address"
|
||||||
msgstr ""
|
msgstr "Ne plus ignorer l'adresse e-mail"
|
||||||
|
|
||||||
#: templates/helpdesk/email_ignore_del.html:8
|
#: templates/helpdesk/email_ignore_del.html:8
|
||||||
#, python-format
|
#, python-format
|
||||||
@ -1363,7 +1365,7 @@ msgid ""
|
|||||||
"Are you sure you wish to stop removing this email address "
|
"Are you sure you wish to stop removing this email address "
|
||||||
"(<em>%(email_address)s</em>) and allow their e-mails to automatically create"
|
"(<em>%(email_address)s</em>) and allow their e-mails to automatically create"
|
||||||
" tickets in your system? You can re-add this e-mail address at any time."
|
" tickets in your system? You can re-add this e-mail address at any time."
|
||||||
msgstr ""
|
msgstr "Êtes-vous certain de vouloir retirer le courriel (<em>%(email_address)s</em>) et de permettre la création automatique des billets dans votre system? Vous pouvez remettre le courriel en tout temps"
|
||||||
|
|
||||||
#: templates/helpdesk/email_ignore_del.html:10
|
#: templates/helpdesk/email_ignore_del.html:10
|
||||||
msgid "Keep Ignoring It"
|
msgid "Keep Ignoring It"
|
||||||
@ -1526,11 +1528,11 @@ msgstr "Statistiques"
|
|||||||
|
|
||||||
#: templates/helpdesk/navigation.html:24
|
#: templates/helpdesk/navigation.html:24
|
||||||
msgid "Saved Query"
|
msgid "Saved Query"
|
||||||
msgstr ""
|
msgstr "Requête sauvegardée"
|
||||||
|
|
||||||
#: templates/helpdesk/navigation.html:39
|
#: templates/helpdesk/navigation.html:39
|
||||||
msgid "Change password"
|
msgid "Change password"
|
||||||
msgstr ""
|
msgstr "Changer le mot de passe"
|
||||||
|
|
||||||
#: templates/helpdesk/navigation.html:50
|
#: templates/helpdesk/navigation.html:50
|
||||||
msgid "Search..."
|
msgid "Search..."
|
||||||
@ -1587,7 +1589,7 @@ msgstr "Impossible d'ouvrir le ticket"
|
|||||||
|
|
||||||
#: templates/helpdesk/public_spam.html:5
|
#: templates/helpdesk/public_spam.html:5
|
||||||
msgid "Sorry, but there has been an error trying to submit your ticket."
|
msgid "Sorry, but there has been an error trying to submit your ticket."
|
||||||
msgstr ""
|
msgstr "Désolé, une erreur est survenue en essayant de soumettre votre ticket."
|
||||||
|
|
||||||
#: templates/helpdesk/public_spam.html:6
|
#: templates/helpdesk/public_spam.html:6
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -1595,13 +1597,13 @@ msgid ""
|
|||||||
"unable to save it. If this is not spam, please press back and re-type your "
|
"unable to save it. If this is not spam, please press back and re-type your "
|
||||||
"message. Be careful to avoid sounding 'spammy', and if you have heaps of "
|
"message. Be careful to avoid sounding 'spammy', and if you have heaps of "
|
||||||
"links please try removing them if possible."
|
"links please try removing them if possible."
|
||||||
msgstr ""
|
msgstr "Notre système a classé votre soumission comme <strong>spam</strong>, alors nous dans l’impossibilité de le sauvegarder. Si ceci n’est pas du spam, svp appuyer recul et retaper votre message en s’assurant de ne pas être « Spammy », si vous avez beaucoup de Liens, svp les retirés."
|
||||||
|
|
||||||
#: templates/helpdesk/public_spam.html:7
|
#: templates/helpdesk/public_spam.html:7
|
||||||
msgid ""
|
msgid ""
|
||||||
"We are sorry for any inconvenience, however this check is required to avoid "
|
"We are sorry for any inconvenience, however this check is required to avoid "
|
||||||
"our helpdesk resources being overloaded by spammers."
|
"our helpdesk resources being overloaded by spammers."
|
||||||
msgstr ""
|
msgstr "Nous somme désolé pour cette inconvénients, mais cette vérification est nécessaire pour évité que notre ressource Helpdesk soit inondée par les spammeur"
|
||||||
|
|
||||||
#: templates/helpdesk/public_view_form.html:8
|
#: templates/helpdesk/public_view_form.html:8
|
||||||
msgid "Error:"
|
msgid "Error:"
|
||||||
@ -1682,7 +1684,7 @@ msgstr "Rapports par File"
|
|||||||
|
|
||||||
#: templates/helpdesk/report_index.html:27 views/staff.py:1049
|
#: templates/helpdesk/report_index.html:27 views/staff.py:1049
|
||||||
msgid "Days until ticket closed by Month"
|
msgid "Days until ticket closed by Month"
|
||||||
msgstr ""
|
msgstr "Jours avant fermeture d'un ticket par mois"
|
||||||
|
|
||||||
#: templates/helpdesk/report_output.html:19
|
#: templates/helpdesk/report_output.html:19
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -1695,7 +1697,7 @@ msgstr "Selectionnez une requête :"
|
|||||||
|
|
||||||
#: templates/helpdesk/report_output.html:26
|
#: templates/helpdesk/report_output.html:26
|
||||||
msgid "Filter Report"
|
msgid "Filter Report"
|
||||||
msgstr ""
|
msgstr "Filtrer le rapport"
|
||||||
|
|
||||||
#: templates/helpdesk/report_output.html:29
|
#: templates/helpdesk/report_output.html:29
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -1739,7 +1741,7 @@ msgid ""
|
|||||||
"all tickets, for each of the queues in your helpdesk. For example, if you "
|
"all tickets, for each of the queues in your helpdesk. For example, if you "
|
||||||
"manage the staff who utilise a particular queue, this may be used to view "
|
"manage the staff who utilise a particular queue, this may be used to view "
|
||||||
"new tickets coming into that queue."
|
"new tickets coming into that queue."
|
||||||
msgstr ""
|
msgstr "Ces flux RSS vous permettent d’avoir un sommaire de vos billets ou tous les billets, pour chacune des files dans votre helpdesk. Par exemple si vous êtes responsable d’une file particulière, ceci vous permet de visionner tous les nouveaux billet entrant."
|
||||||
|
|
||||||
#: templates/helpdesk/rss_list.html:23
|
#: templates/helpdesk/rss_list.html:23
|
||||||
msgid "Per-Queue Feeds"
|
msgid "Per-Queue Feeds"
|
||||||
@ -1759,7 +1761,7 @@ msgstr "Modifier les paramètres systèmes"
|
|||||||
|
|
||||||
#: templates/helpdesk/system_settings.html:8
|
#: templates/helpdesk/system_settings.html:8
|
||||||
msgid "The following items can be maintained by you or other superusers:"
|
msgid "The following items can be maintained by you or other superusers:"
|
||||||
msgstr ""
|
msgstr "Les items suivant peuvent être maintenus par vous ou par des super usagers :"
|
||||||
|
|
||||||
#: templates/helpdesk/system_settings.html:11
|
#: templates/helpdesk/system_settings.html:11
|
||||||
msgid "E-Mail Ignore list"
|
msgid "E-Mail Ignore list"
|
||||||
@ -1889,7 +1891,7 @@ msgstr "\n<h2>Ajouter Ticket CC</h2>\n\n<p>To automatically send an email to a u
|
|||||||
|
|
||||||
#: templates/helpdesk/ticket_cc_add.html:21
|
#: templates/helpdesk/ticket_cc_add.html:21
|
||||||
msgid "Save Ticket CC"
|
msgid "Save Ticket CC"
|
||||||
msgstr ""
|
msgstr "CC sauvegarder billet"
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_cc_del.html:3
|
#: templates/helpdesk/ticket_cc_del.html:3
|
||||||
msgid "Delete Ticket CC"
|
msgid "Delete Ticket CC"
|
||||||
@ -2011,13 +2013,13 @@ msgstr "Cliquez ici pour ajouter / supprimer des personnes qui pourrait recevoir
|
|||||||
|
|
||||||
#: templates/helpdesk/ticket_desc_table.html:53
|
#: templates/helpdesk/ticket_desc_table.html:53
|
||||||
msgid "Subscribe"
|
msgid "Subscribe"
|
||||||
msgstr ""
|
msgstr "Souscrire"
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_desc_table.html:53
|
#: templates/helpdesk/ticket_desc_table.html:53
|
||||||
msgid ""
|
msgid ""
|
||||||
"Click here to subscribe yourself to this ticket, if you want to receive an "
|
"Click here to subscribe yourself to this ticket, if you want to receive an "
|
||||||
"e-mail whenever this ticket is updated."
|
"e-mail whenever this ticket is updated."
|
||||||
msgstr ""
|
msgstr "Cliquez ici pour souscrire à ce ticket si vous souhaitez recevoir un e-mail dès que ce dernier est mis à jour."
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_desc_table.html:57
|
#: templates/helpdesk/ticket_desc_table.html:57
|
||||||
msgid "Dependencies"
|
msgid "Dependencies"
|
||||||
@ -2063,15 +2065,15 @@ msgstr "Mots-clés"
|
|||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:72
|
#: templates/helpdesk/ticket_list.html:72
|
||||||
msgid "Date Range"
|
msgid "Date Range"
|
||||||
msgstr ""
|
msgstr "Période temporelle"
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:100
|
#: templates/helpdesk/ticket_list.html:100
|
||||||
msgid "Reverse"
|
msgid "Reverse"
|
||||||
msgstr ""
|
msgstr "Renverser"
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:102
|
#: templates/helpdesk/ticket_list.html:102
|
||||||
msgid "Ordering applied to tickets"
|
msgid "Ordering applied to tickets"
|
||||||
msgstr ""
|
msgstr "Tri appliqué aux tickets"
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:107
|
#: templates/helpdesk/ticket_list.html:107
|
||||||
msgid "Owner(s)"
|
msgid "Owner(s)"
|
||||||
@ -2079,11 +2081,11 @@ msgstr "Propriétaire(s)"
|
|||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:111
|
#: templates/helpdesk/ticket_list.html:111
|
||||||
msgid "(ME)"
|
msgid "(ME)"
|
||||||
msgstr ""
|
msgstr "(MOI)"
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:115
|
#: templates/helpdesk/ticket_list.html:115
|
||||||
msgid "Ctrl-Click to select multiple options"
|
msgid "Ctrl-Click to select multiple options"
|
||||||
msgstr ""
|
msgstr "Ctrl-Click pour sélectionner plusieurs options"
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:120
|
#: templates/helpdesk/ticket_list.html:120
|
||||||
msgid "Queue(s)"
|
msgid "Queue(s)"
|
||||||
@ -2092,7 +2094,7 @@ msgstr "File(s) d'attente"
|
|||||||
#: templates/helpdesk/ticket_list.html:121
|
#: templates/helpdesk/ticket_list.html:121
|
||||||
#: templates/helpdesk/ticket_list.html:127
|
#: templates/helpdesk/ticket_list.html:127
|
||||||
msgid "Ctrl-click to select multiple options"
|
msgid "Ctrl-click to select multiple options"
|
||||||
msgstr ""
|
msgstr "Ctrl-click pour sélectionner plusieurs options"
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:126
|
#: templates/helpdesk/ticket_list.html:126
|
||||||
msgid "Status(es)"
|
msgid "Status(es)"
|
||||||
@ -2100,21 +2102,21 @@ msgstr "État(s)"
|
|||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:132
|
#: templates/helpdesk/ticket_list.html:132
|
||||||
msgid "Date (From)"
|
msgid "Date (From)"
|
||||||
msgstr ""
|
msgstr "Date (du)"
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:133
|
#: templates/helpdesk/ticket_list.html:133
|
||||||
msgid "Date (To)"
|
msgid "Date (To)"
|
||||||
msgstr ""
|
msgstr "Date (au)"
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:134
|
#: templates/helpdesk/ticket_list.html:134
|
||||||
msgid "Use YYYY-MM-DD date format, eg 2011-05-29"
|
msgid "Use YYYY-MM-DD date format, eg 2011-05-29"
|
||||||
msgstr ""
|
msgstr "Utilisez le format de date AAAA-MM-JJ, ex 2011-05-29"
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:140
|
#: templates/helpdesk/ticket_list.html:140
|
||||||
msgid ""
|
msgid ""
|
||||||
"Keywords are case-insensitive, and will be looked for in the title, body and"
|
"Keywords are case-insensitive, and will be looked for in the title, body and"
|
||||||
" submitter fields."
|
" submitter fields."
|
||||||
msgstr ""
|
msgstr "Les mots clés sont insensibles à la case et seront appliqués aux champs titre, corps de texte et rapporteur."
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:144
|
#: templates/helpdesk/ticket_list.html:144
|
||||||
msgid "Apply Filter"
|
msgid "Apply Filter"
|
||||||
@ -2123,14 +2125,14 @@ msgstr "Appliquer le filtre"
|
|||||||
#: templates/helpdesk/ticket_list.html:146
|
#: templates/helpdesk/ticket_list.html:146
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "You are currently viewing saved query <strong>\"%(query_name)s\"</strong>."
|
msgid "You are currently viewing saved query <strong>\"%(query_name)s\"</strong>."
|
||||||
msgstr ""
|
msgstr "Vous visionnez la requête <strong>\\\"%(query_name)s\\\"</strong>."
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:149
|
#: templates/helpdesk/ticket_list.html:149
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"<a href='../reports/?saved_query=%(query_id)s'>Run a report</a> on this "
|
"<a href='../reports/?saved_query=%(query_id)s'>Run a report</a> on this "
|
||||||
"query to see stats and charts for the data listed below."
|
"query to see stats and charts for the data listed below."
|
||||||
msgstr ""
|
msgstr "<a href='../reports/?saved_query=%(query_id)s'>Exécuté un rapport surcette requête pour voir les statistique et graphique pour les data ci-dessous."
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:162
|
#: templates/helpdesk/ticket_list.html:162
|
||||||
#: templates/helpdesk/ticket_list.html:181
|
#: templates/helpdesk/ticket_list.html:181
|
||||||
@ -2197,7 +2199,7 @@ msgstr "Aucune"
|
|||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:260
|
#: templates/helpdesk/ticket_list.html:260
|
||||||
msgid "Inverse"
|
msgid "Inverse"
|
||||||
msgstr ""
|
msgstr "Inverser"
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:262
|
#: templates/helpdesk/ticket_list.html:262
|
||||||
msgid "With Selected Tickets:"
|
msgid "With Selected Tickets:"
|
||||||
@ -2235,7 +2237,7 @@ msgstr "Modifier les paramètres de l'utilisateur"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"Use the following options to change the way your helpdesk system works for "
|
"Use the following options to change the way your helpdesk system works for "
|
||||||
"you. These settings do not impact any other user."
|
"you. These settings do not impact any other user."
|
||||||
msgstr ""
|
msgstr "Utilisez les options ci-dessous pour changer personnaliser le fonctionnement du centre d'assistance. Ces paramètres n'impactent pas les autres utilisateurs."
|
||||||
|
|
||||||
#: templates/helpdesk/user_settings.html:14
|
#: templates/helpdesk/user_settings.html:14
|
||||||
msgid "Save Options"
|
msgid "Save Options"
|
||||||
@ -2260,7 +2262,7 @@ msgstr "Identifiant Helpdesk"
|
|||||||
|
|
||||||
#: templates/helpdesk/registration/login.html:14
|
#: templates/helpdesk/registration/login.html:14
|
||||||
msgid "To log in simply enter your username and password below."
|
msgid "To log in simply enter your username and password below."
|
||||||
msgstr ""
|
msgstr "Renseignez votre nom d'utilisateur et votre mot de passe ci-dessous pour vous connecter."
|
||||||
|
|
||||||
#: templates/helpdesk/registration/login.html:17
|
#: templates/helpdesk/registration/login.html:17
|
||||||
msgid "Your username and password didn't match. Please try again."
|
msgid "Your username and password didn't match. Please try again."
|
||||||
|
Binary file not shown.
@ -4,14 +4,15 @@
|
|||||||
#
|
#
|
||||||
# Translators:
|
# Translators:
|
||||||
# Translators:
|
# Translators:
|
||||||
|
# Alan Pevec <apevec@gmail.com>, 2016
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: django-helpdesk\n"
|
"Project-Id-Version: django-helpdesk\n"
|
||||||
"Report-Msgid-Bugs-To: http://github.com/RossP/django-helpdesk/issues\n"
|
"Report-Msgid-Bugs-To: http://github.com/RossP/django-helpdesk/issues\n"
|
||||||
"POT-Creation-Date: 2014-07-26 14:14+0200\n"
|
"POT-Creation-Date: 2014-07-26 14:14+0200\n"
|
||||||
"PO-Revision-Date: 2014-08-01 09:58+0000\n"
|
"PO-Revision-Date: 2016-10-09 16:56+0000\n"
|
||||||
"Last-Translator: Ross Poulton <ross@rossp.org>\n"
|
"Last-Translator: Alan Pevec <apevec@gmail.com>\n"
|
||||||
"Language-Team: Croatian (http://www.transifex.com/projects/p/django-helpdesk/language/hr/)\n"
|
"Language-Team: Croatian (http://www.transifex.com/rossp/django-helpdesk/language/hr/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
@ -27,29 +28,29 @@ msgstr ""
|
|||||||
#: templates/helpdesk/ticket_list.html:225 views/staff.py:1032
|
#: templates/helpdesk/ticket_list.html:225 views/staff.py:1032
|
||||||
#: views/staff.py:1038 views/staff.py:1044 views/staff.py:1050
|
#: views/staff.py:1038 views/staff.py:1044 views/staff.py:1050
|
||||||
msgid "Queue"
|
msgid "Queue"
|
||||||
msgstr ""
|
msgstr "red"
|
||||||
|
|
||||||
#: forms.py:137
|
#: forms.py:137
|
||||||
msgid "Summary of the problem"
|
msgid "Summary of the problem"
|
||||||
msgstr ""
|
msgstr "sažetak problema"
|
||||||
|
|
||||||
#: forms.py:142
|
#: forms.py:142
|
||||||
msgid "Submitter E-Mail Address"
|
msgid "Submitter E-Mail Address"
|
||||||
msgstr ""
|
msgstr "adresa podnositelja"
|
||||||
|
|
||||||
#: forms.py:144
|
#: forms.py:144
|
||||||
msgid ""
|
msgid ""
|
||||||
"This e-mail address will receive copies of all public updates to this "
|
"This e-mail address will receive copies of all public updates to this "
|
||||||
"ticket."
|
"ticket."
|
||||||
msgstr ""
|
msgstr "Ova adresa će primiti kopije svih javnih ažuriranja ovog problema."
|
||||||
|
|
||||||
#: forms.py:150
|
#: forms.py:150
|
||||||
msgid "Description of Issue"
|
msgid "Description of Issue"
|
||||||
msgstr ""
|
msgstr "opis problema"
|
||||||
|
|
||||||
#: forms.py:157
|
#: forms.py:157
|
||||||
msgid "Case owner"
|
msgid "Case owner"
|
||||||
msgstr ""
|
msgstr "vlasnik slučaja"
|
||||||
|
|
||||||
#: forms.py:158
|
#: forms.py:158
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -63,59 +64,59 @@ msgstr ""
|
|||||||
#: templates/helpdesk/ticket_desc_table.html:47
|
#: templates/helpdesk/ticket_desc_table.html:47
|
||||||
#: templates/helpdesk/ticket_list.html:94 views/staff.py:429
|
#: templates/helpdesk/ticket_list.html:94 views/staff.py:429
|
||||||
msgid "Priority"
|
msgid "Priority"
|
||||||
msgstr ""
|
msgstr "prioritet"
|
||||||
|
|
||||||
#: forms.py:167
|
#: forms.py:167
|
||||||
msgid "Please select a priority carefully. If unsure, leave it as '3'."
|
msgid "Please select a priority carefully. If unsure, leave it as '3'."
|
||||||
msgstr ""
|
msgstr "Pažljivo odredite prioritet. Ako niste sigurni, ostavite '3'."
|
||||||
|
|
||||||
#: forms.py:174 forms.py:365 models.py:335 templates/helpdesk/ticket.html:186
|
#: forms.py:174 forms.py:365 models.py:335 templates/helpdesk/ticket.html:186
|
||||||
#: views/staff.py:439
|
#: views/staff.py:439
|
||||||
msgid "Due on"
|
msgid "Due on"
|
||||||
msgstr ""
|
msgstr "Rok izvršenja"
|
||||||
|
|
||||||
#: forms.py:186 forms.py:370
|
#: forms.py:186 forms.py:370
|
||||||
msgid "Attach File"
|
msgid "Attach File"
|
||||||
msgstr ""
|
msgstr "priloži datoteku"
|
||||||
|
|
||||||
#: forms.py:187 forms.py:371
|
#: forms.py:187 forms.py:371
|
||||||
msgid "You can attach a file such as a document or screenshot to this ticket."
|
msgid "You can attach a file such as a document or screenshot to this ticket."
|
||||||
msgstr ""
|
msgstr "Možete priložiti datoteku npr. dokument ili sliku ekrana."
|
||||||
|
|
||||||
#: forms.py:240
|
#: forms.py:240
|
||||||
msgid "Ticket Opened"
|
msgid "Ticket Opened"
|
||||||
msgstr ""
|
msgstr "otvoreni slučaj"
|
||||||
|
|
||||||
#: forms.py:247
|
#: forms.py:247
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Ticket Opened & Assigned to %(name)s"
|
msgid "Ticket Opened & Assigned to %(name)s"
|
||||||
msgstr ""
|
msgstr "Slučaj otvoren i dodijeljen %(name)s"
|
||||||
|
|
||||||
#: forms.py:337
|
#: forms.py:337
|
||||||
msgid "Summary of your query"
|
msgid "Summary of your query"
|
||||||
msgstr ""
|
msgstr "Sažetak Vašeg upita"
|
||||||
|
|
||||||
#: forms.py:342
|
#: forms.py:342
|
||||||
msgid "Your E-Mail Address"
|
msgid "Your E-Mail Address"
|
||||||
msgstr ""
|
msgstr "adresa Vaše elektroničke pošte"
|
||||||
|
|
||||||
#: forms.py:343
|
#: forms.py:343
|
||||||
msgid "We will e-mail you when your ticket is updated."
|
msgid "We will e-mail you when your ticket is updated."
|
||||||
msgstr ""
|
msgstr "Poslat ćemo Vam poruku kad Vaš slučaj bude ažuriran."
|
||||||
|
|
||||||
#: forms.py:348
|
#: forms.py:348
|
||||||
msgid "Description of your issue"
|
msgid "Description of your issue"
|
||||||
msgstr ""
|
msgstr "opis Vašeg prolema"
|
||||||
|
|
||||||
#: forms.py:350
|
#: forms.py:350
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please be as descriptive as possible, including any details we may need to "
|
"Please be as descriptive as possible, including any details we may need to "
|
||||||
"address your query."
|
"address your query."
|
||||||
msgstr ""
|
msgstr "Budite što je moguće jasniji i uključite sve detalje koji nam trebaju za rješavanje Vašeg upita."
|
||||||
|
|
||||||
#: forms.py:358
|
#: forms.py:358
|
||||||
msgid "Urgency"
|
msgid "Urgency"
|
||||||
msgstr ""
|
msgstr "hitnost"
|
||||||
|
|
||||||
#: forms.py:359
|
#: forms.py:359
|
||||||
msgid "Please select a priority carefully."
|
msgid "Please select a priority carefully."
|
||||||
@ -123,11 +124,11 @@ msgstr ""
|
|||||||
|
|
||||||
#: forms.py:419
|
#: forms.py:419
|
||||||
msgid "Ticket Opened Via Web"
|
msgid "Ticket Opened Via Web"
|
||||||
msgstr ""
|
msgstr "Slučaj otvoren na webu"
|
||||||
|
|
||||||
#: forms.py:486
|
#: forms.py:486
|
||||||
msgid "Show Ticket List on Login?"
|
msgid "Show Ticket List on Login?"
|
||||||
msgstr ""
|
msgstr "Prikaži listu slučajeva prilikom prijave?"
|
||||||
|
|
||||||
#: forms.py:487
|
#: forms.py:487
|
||||||
msgid "Display the ticket list upon login? Otherwise, the dashboard is shown."
|
msgid "Display the ticket list upon login? Otherwise, the dashboard is shown."
|
||||||
@ -185,7 +186,7 @@ msgstr ""
|
|||||||
#: templates/helpdesk/ticket.html:178 templates/helpdesk/ticket_list.html:85
|
#: templates/helpdesk/ticket.html:178 templates/helpdesk/ticket_list.html:85
|
||||||
#: templates/helpdesk/ticket_list.html:225 views/staff.py:419
|
#: templates/helpdesk/ticket_list.html:225 views/staff.py:419
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr ""
|
msgstr "naslov"
|
||||||
|
|
||||||
#: models.py:40 models.py:822 models.py:1206
|
#: models.py:40 models.py:822 models.py:1206
|
||||||
msgid "Slug"
|
msgid "Slug"
|
||||||
@ -201,7 +202,7 @@ msgstr ""
|
|||||||
#: templates/helpdesk/email_ignore_list.html:13
|
#: templates/helpdesk/email_ignore_list.html:13
|
||||||
#: templates/helpdesk/ticket_cc_list.html:15
|
#: templates/helpdesk/ticket_cc_list.html:15
|
||||||
msgid "E-Mail Address"
|
msgid "E-Mail Address"
|
||||||
msgstr ""
|
msgstr "adresa elektroničke pošte"
|
||||||
|
|
||||||
#: models.py:49
|
#: models.py:49
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -229,7 +230,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: models.py:71
|
#: models.py:71
|
||||||
msgid "Allow E-Mail Submission?"
|
msgid "Allow E-Mail Submission?"
|
||||||
msgstr ""
|
msgstr "Dozvoli prijave elektroničkom poštom?"
|
||||||
|
|
||||||
#: models.py:74
|
#: models.py:74
|
||||||
msgid "Do you want to poll the e-mail box below for new tickets?"
|
msgid "Do you want to poll the e-mail box below for new tickets?"
|
||||||
@ -273,11 +274,11 @@ msgstr ""
|
|||||||
|
|
||||||
#: models.py:110
|
#: models.py:110
|
||||||
msgid "POP 3"
|
msgid "POP 3"
|
||||||
msgstr ""
|
msgstr "POP 3"
|
||||||
|
|
||||||
#: models.py:110
|
#: models.py:110
|
||||||
msgid "IMAP"
|
msgid "IMAP"
|
||||||
msgstr ""
|
msgstr "IMAP"
|
||||||
|
|
||||||
#: models.py:113
|
#: models.py:113
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -353,30 +354,30 @@ msgstr ""
|
|||||||
|
|
||||||
#: models.py:191 templates/helpdesk/email_ignore_list.html:13
|
#: models.py:191 templates/helpdesk/email_ignore_list.html:13
|
||||||
msgid "Queues"
|
msgid "Queues"
|
||||||
msgstr ""
|
msgstr "redovi"
|
||||||
|
|
||||||
#: models.py:245 templates/helpdesk/dashboard.html:15
|
#: models.py:245 templates/helpdesk/dashboard.html:15
|
||||||
#: templates/helpdesk/ticket.html:138
|
#: templates/helpdesk/ticket.html:138
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
msgstr ""
|
msgstr "otvoreno"
|
||||||
|
|
||||||
#: models.py:246 templates/helpdesk/ticket.html:144
|
#: models.py:246 templates/helpdesk/ticket.html:144
|
||||||
#: templates/helpdesk/ticket.html.py:150 templates/helpdesk/ticket.html:155
|
#: templates/helpdesk/ticket.html.py:150 templates/helpdesk/ticket.html:155
|
||||||
#: templates/helpdesk/ticket.html.py:159
|
#: templates/helpdesk/ticket.html.py:159
|
||||||
msgid "Reopened"
|
msgid "Reopened"
|
||||||
msgstr ""
|
msgstr "ponovno otvoreno"
|
||||||
|
|
||||||
#: models.py:247 templates/helpdesk/dashboard.html:15
|
#: models.py:247 templates/helpdesk/dashboard.html:15
|
||||||
#: templates/helpdesk/ticket.html:139 templates/helpdesk/ticket.html.py:145
|
#: templates/helpdesk/ticket.html:139 templates/helpdesk/ticket.html.py:145
|
||||||
#: templates/helpdesk/ticket.html:151
|
#: templates/helpdesk/ticket.html:151
|
||||||
msgid "Resolved"
|
msgid "Resolved"
|
||||||
msgstr ""
|
msgstr "riješeno"
|
||||||
|
|
||||||
#: models.py:248 templates/helpdesk/dashboard.html:15
|
#: models.py:248 templates/helpdesk/dashboard.html:15
|
||||||
#: templates/helpdesk/ticket.html:140 templates/helpdesk/ticket.html.py:146
|
#: templates/helpdesk/ticket.html:140 templates/helpdesk/ticket.html.py:146
|
||||||
#: templates/helpdesk/ticket.html:152 templates/helpdesk/ticket.html.py:156
|
#: templates/helpdesk/ticket.html:152 templates/helpdesk/ticket.html.py:156
|
||||||
msgid "Closed"
|
msgid "Closed"
|
||||||
msgstr ""
|
msgstr "zatvoreno"
|
||||||
|
|
||||||
#: models.py:249 templates/helpdesk/ticket.html:141
|
#: models.py:249 templates/helpdesk/ticket.html:141
|
||||||
#: templates/helpdesk/ticket.html.py:147 templates/helpdesk/ticket.html:160
|
#: templates/helpdesk/ticket.html.py:147 templates/helpdesk/ticket.html:160
|
||||||
@ -695,7 +696,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: models.py:788
|
#: models.py:788
|
||||||
msgid "HTML"
|
msgid "HTML"
|
||||||
msgstr ""
|
msgstr "HTML"
|
||||||
|
|
||||||
#: models.py:789
|
#: models.py:789
|
||||||
msgid "The same context is available here as in plain_text, above."
|
msgid "The same context is available here as in plain_text, above."
|
||||||
@ -724,15 +725,15 @@ msgstr ""
|
|||||||
#: models.py:849 templates/helpdesk/kb_index.html:11
|
#: models.py:849 templates/helpdesk/kb_index.html:11
|
||||||
#: templates/helpdesk/public_homepage.html:11
|
#: templates/helpdesk/public_homepage.html:11
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr ""
|
msgstr "kategorija"
|
||||||
|
|
||||||
#: models.py:858
|
#: models.py:858
|
||||||
msgid "Question"
|
msgid "Question"
|
||||||
msgstr ""
|
msgstr "pitanje"
|
||||||
|
|
||||||
#: models.py:862
|
#: models.py:862
|
||||||
msgid "Answer"
|
msgid "Answer"
|
||||||
msgstr ""
|
msgstr "odgovor"
|
||||||
|
|
||||||
#: models.py:866
|
#: models.py:866
|
||||||
msgid "Votes"
|
msgid "Votes"
|
||||||
@ -752,7 +753,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: models.py:878
|
#: models.py:878
|
||||||
msgid "Last Updated"
|
msgid "Last Updated"
|
||||||
msgstr ""
|
msgstr "zadnje ažuriranje"
|
||||||
|
|
||||||
#: models.py:879
|
#: models.py:879
|
||||||
msgid "The date on which this question was most recently changed."
|
msgid "The date on which this question was most recently changed."
|
||||||
@ -1087,7 +1088,7 @@ msgstr ""
|
|||||||
#: templates/helpdesk/attribution.html:2
|
#: templates/helpdesk/attribution.html:2
|
||||||
msgid ""
|
msgid ""
|
||||||
"<a href='https://github.com/rossp/django-helpdesk'>django-helpdesk</a>."
|
"<a href='https://github.com/rossp/django-helpdesk'>django-helpdesk</a>."
|
||||||
msgstr ""
|
msgstr "<a href='https://github.com/rossp/django-helpdesk'>django-helpdesk</a>."
|
||||||
|
|
||||||
#: templates/helpdesk/base.html:10
|
#: templates/helpdesk/base.html:10
|
||||||
msgid "Powered by django-helpdesk"
|
msgid "Powered by django-helpdesk"
|
||||||
@ -1110,7 +1111,7 @@ msgstr ""
|
|||||||
#: templates/helpdesk/base.html:52 templates/helpdesk/public_base.html:6
|
#: templates/helpdesk/base.html:52 templates/helpdesk/public_base.html:6
|
||||||
#: templates/helpdesk/public_base.html:18
|
#: templates/helpdesk/public_base.html:18
|
||||||
msgid "Helpdesk"
|
msgid "Helpdesk"
|
||||||
msgstr ""
|
msgstr "Helpdesk"
|
||||||
|
|
||||||
#: templates/helpdesk/base.html:62 templates/helpdesk/rss_list.html:9
|
#: templates/helpdesk/base.html:62 templates/helpdesk/rss_list.html:9
|
||||||
#: templates/helpdesk/rss_list.html:12 templates/helpdesk/rss_list.html:15
|
#: templates/helpdesk/rss_list.html:12 templates/helpdesk/rss_list.html:15
|
||||||
@ -2053,7 +2054,7 @@ msgstr ""
|
|||||||
#: templates/helpdesk/ticket_list.html:71
|
#: templates/helpdesk/ticket_list.html:71
|
||||||
#: templates/helpdesk/ticket_list.html:139
|
#: templates/helpdesk/ticket_list.html:139
|
||||||
msgid "Keywords"
|
msgid "Keywords"
|
||||||
msgstr ""
|
msgstr "ključne riječi"
|
||||||
|
|
||||||
#: templates/helpdesk/ticket_list.html:72
|
#: templates/helpdesk/ticket_list.html:72
|
||||||
msgid "Date Range"
|
msgid "Date Range"
|
||||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||||
|
|
||||||
@ -43,11 +43,12 @@ class Command(BaseCommand):
|
|||||||
default=False,
|
default=False,
|
||||||
dest='escalate-verbosely',
|
dest='escalate-verbosely',
|
||||||
help='Display a list of dates excluded'),
|
help='Display a list of dates excluded'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
days = options['days']
|
days = options['days']
|
||||||
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)
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ class Command(BaseCommand):
|
|||||||
make_option(
|
make_option(
|
||||||
'--queues', '-q',
|
'--queues', '-q',
|
||||||
help='Queues to include (default: all). Use queue slugs'),
|
help='Queues to include (default: all). Use queue slugs'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
queue_slugs = options['queues']
|
queue_slugs = options['queues']
|
||||||
@ -71,4 +72,3 @@ class Command(BaseCommand):
|
|||||||
)
|
)
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
self.stdout.write(" .. permission already existed, skipping")
|
self.stdout.write(" .. permission already existed, skipping")
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ class Command(BaseCommand):
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
help='Display a list of dates excluded'),
|
help='Display a list of dates excluded'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
verbose = False
|
verbose = False
|
||||||
@ -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,24 +83,23 @@ 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()
|
||||||
t.priority -= 1
|
t.priority -= 1
|
||||||
@ -114,7 +114,7 @@ def escalate_tickets(queues, verbose):
|
|||||||
recipients=t.submitter_email,
|
recipients=t.submitter_email,
|
||||||
sender=t.queue.from_address,
|
sender=t.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if t.queue.updated_ticket_cc:
|
if t.queue.updated_ticket_cc:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
@ -123,7 +123,7 @@ def escalate_tickets(queues, verbose):
|
|||||||
recipients=t.queue.updated_ticket_cc,
|
recipients=t.queue.updated_ticket_cc,
|
||||||
sender=t.queue.from_address,
|
sender=t.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if t.assigned_to:
|
if t.assigned_to:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
@ -132,19 +132,19 @@ def escalate_tickets(queues, verbose):
|
|||||||
recipients=t.assigned_to.email,
|
recipients=t.assigned_to.email,
|
||||||
sender=t.queue.from_address,
|
sender=t.queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
print(" - Esclating %s from %s>%s" % (
|
print(" - Esclating %s from %s>%s" % (
|
||||||
t.ticket,
|
t.ticket,
|
||||||
t.priority+1,
|
t.priority + 1,
|
||||||
t.priority
|
t.priority
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
f = FollowUp(
|
f = FollowUp(
|
||||||
ticket = t,
|
ticket=t,
|
||||||
title = 'Ticket Escalated',
|
title='Ticket Escalated',
|
||||||
date=timezone.now(),
|
date=timezone.now(),
|
||||||
public=True,
|
public=True,
|
||||||
comment=_('Ticket escalated after %s days' % q.escalate_days),
|
comment=_('Ticket escalated after %s days' % q.escalate_days),
|
||||||
@ -152,10 +152,10 @@ def escalate_tickets(queues, verbose):
|
|||||||
f.save()
|
f.save()
|
||||||
|
|
||||||
tc = TicketChange(
|
tc = TicketChange(
|
||||||
followup = f,
|
followup=f,
|
||||||
field = _('Priority'),
|
field=_('Priority'),
|
||||||
old_value = t.priority + 1,
|
old_value=t.priority + 1,
|
||||||
new_value = t.priority,
|
new_value=t.priority,
|
||||||
)
|
)
|
||||||
tc.save()
|
tc.save()
|
||||||
|
|
||||||
|
@ -5,11 +5,11 @@ Jutda Helpdesk - A Django powered ticket tracker for small enterprise.
|
|||||||
(c) Copyright 2008 Jutda. All Rights Reserved. See LICENSE for details.
|
(c) Copyright 2008 Jutda. All Rights Reserved. See LICENSE for details.
|
||||||
|
|
||||||
scripts/get_email.py - Designed to be run from cron, this script checks the
|
scripts/get_email.py - Designed to be run from cron, this script checks the
|
||||||
POP and IMAP boxes defined for the queues within a
|
POP and IMAP boxes, or a local mailbox directory,
|
||||||
|
defined for the queues within a
|
||||||
helpdesk, creating tickets from the new messages (or
|
helpdesk, creating tickets from the new messages (or
|
||||||
adding to existing tickets if needed)
|
adding to existing tickets if needed)
|
||||||
"""
|
"""
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import email
|
import email
|
||||||
import imaplib
|
import imaplib
|
||||||
@ -17,6 +17,8 @@ import mimetypes
|
|||||||
import poplib
|
import poplib
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
|
from os import listdir, unlink
|
||||||
|
from os.path import isfile, join
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from email.header import decode_header
|
from email.header import decode_header
|
||||||
@ -25,10 +27,12 @@ from optparse import make_option
|
|||||||
|
|
||||||
from email_reply_parser import EmailReplyParser
|
from email_reply_parser import EmailReplyParser
|
||||||
|
|
||||||
|
from django import VERSION
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
from django.utils import six
|
||||||
from helpdesk import settings
|
from helpdesk import settings
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -39,20 +43,46 @@ except ImportError:
|
|||||||
from helpdesk.lib import send_templated_mail, safe_template_context
|
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
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from time import ctime
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
self.option_list += (
|
# Django 1.7 uses different way to specify options than 1.8+
|
||||||
|
if VERSION < (1, 8):
|
||||||
|
self.option_list += (
|
||||||
make_option(
|
make_option(
|
||||||
'--quiet', '-q',
|
'--quiet',
|
||||||
default=False,
|
default=False,
|
||||||
action='store_true',
|
action='store_true',
|
||||||
|
dest='quiet',
|
||||||
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 django-helpdesk queues and process e-mails via POP3/IMAP or ' \
|
||||||
|
'from a local mailbox directory as required, feeding them into the helpdesk.'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--quiet',
|
||||||
|
action='store_true',
|
||||||
|
dest='quiet',
|
||||||
|
default=False,
|
||||||
|
help='Hide details about each queue/message as they are processed',
|
||||||
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
quiet = options.get('quiet', False)
|
quiet = options.get('quiet', False)
|
||||||
@ -64,118 +94,205 @@ def process_email(quiet=False):
|
|||||||
email_box_type__isnull=False,
|
email_box_type__isnull=False,
|
||||||
allow_email_submission=True):
|
allow_email_submission=True):
|
||||||
|
|
||||||
|
logger = logging.getLogger('django.helpdesk.queue.' + q.slug)
|
||||||
|
if not q.logging_type or q.logging_type == 'none':
|
||||||
|
logging.disable(logging.CRITICAL) #disable all messages
|
||||||
|
elif q.logging_type == 'info':
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
elif q.logging_type == 'warn':
|
||||||
|
logger.setLevel(logging.WARN)
|
||||||
|
elif q.logging_type == 'error':
|
||||||
|
logger.setLevel(logging.ERROR)
|
||||||
|
elif q.logging_type == 'crit':
|
||||||
|
logger.setLevel(logging.CRITICAL)
|
||||||
|
elif q.logging_type == 'debug':
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
if quiet:
|
||||||
|
logger.propagate = False # do not propagate to root logger that would log to console
|
||||||
|
logdir = q.logging_dir or '/var/log/helpdesk/'
|
||||||
|
handler = logging.FileHandler(logdir + q.slug + '_get_email.log')
|
||||||
|
logger.addHandler(handler)
|
||||||
|
|
||||||
if not q.email_box_last_check:
|
if not q.email_box_last_check:
|
||||||
q.email_box_last_check = timezone.now()-timedelta(minutes=30)
|
q.email_box_last_check = timezone.now() - timedelta(minutes=30)
|
||||||
|
|
||||||
if not q.email_box_interval:
|
if not q.email_box_interval:
|
||||||
q.email_box_interval = 0
|
q.email_box_interval = 0
|
||||||
|
|
||||||
|
|
||||||
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():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
process_queue(q, quiet=quiet)
|
process_queue(q, logger=logger)
|
||||||
|
|
||||||
q.email_box_last_check = timezone.now()
|
q.email_box_last_check = timezone.now()
|
||||||
q.save()
|
q.save()
|
||||||
|
|
||||||
|
|
||||||
def process_queue(q, quiet=False):
|
def process_queue(q, logger):
|
||||||
if not quiet:
|
logger.info("***** %s: Begin processing mail for django-helpdesk" % ctime())
|
||||||
print("Processing: %s" % q)
|
|
||||||
|
|
||||||
if q.socks_proxy_type and q.socks_proxy_host and q.socks_proxy_port:
|
if q.socks_proxy_type and q.socks_proxy_host and q.socks_proxy_port:
|
||||||
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.")
|
no_socks_msg = "Queue has been configured with proxy settings, but no socks " \
|
||||||
|
"library was installed. Try to install PySocks via PyPI."
|
||||||
|
logger.error(no_socks_msg)
|
||||||
|
raise ImportError(no_socks_msg)
|
||||||
|
|
||||||
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
|
if six.PY2:
|
||||||
|
socket.socket = socket._socketobject
|
||||||
|
elif six.PY3:
|
||||||
|
import _socket
|
||||||
|
socket.socket = _socket.socket
|
||||||
|
|
||||||
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))
|
||||||
|
|
||||||
|
logger.info("Attempting POP3 server login")
|
||||||
|
|
||||||
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]
|
||||||
|
logger.info("Received %s messages from POP3 server" % str(len(messagesInfo)))
|
||||||
|
|
||||||
for msg in messagesInfo:
|
for msg in messagesInfo:
|
||||||
msgNum = msg.split(" ")[0]
|
msgNum = msg.split(" ")[0]
|
||||||
msgSize = msg.split(" ")[1]
|
logger.info("Processing message %s" % str(msgNum))
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
if ticket:
|
if ticket:
|
||||||
server.dele(msgNum)
|
server.dele(msgNum)
|
||||||
|
logger.info("Successfully processed message %s, deleted from POP3 server" % str(msgNum))
|
||||||
|
else:
|
||||||
|
logger.warn("Message %s was not successfully processed, and will be left on POP3 server" % str(msgNum))
|
||||||
|
|
||||||
server.quit()
|
server.quit()
|
||||||
|
|
||||||
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)
|
logger.info("Attempting IMAP server login")
|
||||||
|
|
||||||
|
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')
|
||||||
if data:
|
if data:
|
||||||
msgnums = data[0].split()
|
msgnums = data[0].split()
|
||||||
|
logger.info("Received %s messages from IMAP server" % str(len(msgnums)))
|
||||||
for num in msgnums:
|
for num in msgnums:
|
||||||
|
logger.info("Processing message %s" % str(num))
|
||||||
status, data = server.fetch(num, '(RFC822)')
|
status, data = server.fetch(num, '(RFC822)')
|
||||||
ticket = ticket_from_message(message=data[0][1], queue=q, quiet=quiet)
|
ticket = ticket_from_message(message=data[0][1], queue=q)
|
||||||
if ticket:
|
if ticket:
|
||||||
server.store(num, '+FLAGS', '\\Deleted')
|
server.store(num, '+FLAGS', '\\Deleted')
|
||||||
|
logger.info("Successfully processed message %s, deleted from IMAP server" % str(msgNum))
|
||||||
|
else:
|
||||||
|
logger.warn("Message %s was not successfully processed, and will be left on IMAP server" % str(msgNum))
|
||||||
|
|
||||||
server.expunge()
|
server.expunge()
|
||||||
server.close()
|
server.close()
|
||||||
server.logout()
|
server.logout()
|
||||||
|
|
||||||
|
elif email_box_type == 'local':
|
||||||
|
mail_dir = q.email_box_local_dir or '/var/lib/mail/helpdesk/'
|
||||||
|
mail = [join(mail_dir, f) for f in listdir(mail_dir) if isfile(join(mail_dir, f))]
|
||||||
|
logger.info("Found %s messages in local mailbox directory" % str(len(mail)))
|
||||||
|
for m in mail:
|
||||||
|
logger.info("Processing message %s" % str(m))
|
||||||
|
f = open(m, 'r')
|
||||||
|
ticket = ticket_from_message(message=f.read(), queue=q, logger=logger)
|
||||||
|
if ticket:
|
||||||
|
logger.info("Successfully processed message %s, ticket/comment created." % str(m))
|
||||||
|
try:
|
||||||
|
#unlink(m) #delete message file if ticket was successful
|
||||||
|
logger.info("Successfully deleted message %s." % str(m))
|
||||||
|
except:
|
||||||
|
logger.error("Unable to delete message %s." % str(m))
|
||||||
|
else:
|
||||||
|
logger.warn("Message %s was not successfully processed, and will be left in local directory" % str(m))
|
||||||
|
|
||||||
|
|
||||||
def decodeUnknown(charset, string):
|
def decodeUnknown(charset, string):
|
||||||
if not charset:
|
if six.PY2:
|
||||||
try:
|
if not charset:
|
||||||
return string.decode('utf-8','ignore')
|
try:
|
||||||
except:
|
return string.decode('utf-8', 'ignore')
|
||||||
return string.decode('iso8859-1','ignore')
|
except:
|
||||||
return unicode(string, charset)
|
return string.decode('iso8859-1', 'ignore')
|
||||||
|
return unicode(string, charset)
|
||||||
|
elif six.PY3:
|
||||||
|
if type(string) is not str:
|
||||||
|
if not charset:
|
||||||
|
try:
|
||||||
|
return str(string, encoding='utf-8', errors='replace')
|
||||||
|
except:
|
||||||
|
return str(string, encoding='iso8859-1', errors='replace')
|
||||||
|
return str(string, encoding=charset)
|
||||||
|
return string
|
||||||
|
|
||||||
|
|
||||||
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])
|
if six.PY2:
|
||||||
|
return u' '.join([unicode(msg, charset or 'utf-8') for msg, charset in decoded])
|
||||||
|
elif six.PY3:
|
||||||
|
return u' '.join([str(msg,encoding=charset,errors='replace') if charset else str(msg) for msg, charset in decoded])
|
||||||
|
|
||||||
def ticket_from_message(message, queue, quiet):
|
|
||||||
|
def ticket_from_message(message, queue, logger):
|
||||||
# '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))
|
||||||
@ -192,11 +309,13 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
matchobj = re.match(r".*\["+queue.slug+"-(?P<id>\d+)\]", subject)
|
matchobj = re.match(r".*\[" + queue.slug + "-(?P<id>\d+)\]", subject)
|
||||||
if matchobj:
|
if matchobj:
|
||||||
# This is a reply or forward.
|
# This is a reply or forward.
|
||||||
ticket = matchobj.group('id')
|
ticket = matchobj.group('id')
|
||||||
|
logger.info("Matched tracking ID %s-%s" % (queue.slug, ticket))
|
||||||
else:
|
else:
|
||||||
|
logger.info("No tracking ID matched.")
|
||||||
ticket = None
|
ticket = None
|
||||||
|
|
||||||
counter = 0
|
counter = 0
|
||||||
@ -210,11 +329,14 @@ 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)))
|
||||||
|
logger.debug("Discovered plain text MIME part")
|
||||||
else:
|
else:
|
||||||
body_html = part.get_payload(decode=True)
|
body_html = part.get_payload(decode=True)
|
||||||
|
logger.debug("Discovered HTML MIME part")
|
||||||
else:
|
else:
|
||||||
if not name:
|
if not name:
|
||||||
ext = mimetypes.guess_extension(part.get_content_type())
|
ext = mimetypes.guess_extension(part.get_content_type())
|
||||||
@ -224,14 +346,15 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
'filename': name,
|
'filename': name,
|
||||||
'content': part.get_payload(decode=True),
|
'content': part.get_payload(decode=True),
|
||||||
'type': part.get_content_type()},
|
'type': part.get_content_type()},
|
||||||
)
|
)
|
||||||
|
logger.debug("Found MIME attachment %s" % name)
|
||||||
|
|
||||||
counter += 1
|
counter += 1
|
||||||
|
|
||||||
if body_plain:
|
if body_plain:
|
||||||
body = body_plain
|
body = body_plain
|
||||||
else:
|
else:
|
||||||
body = _('No plain-text email body available. Please see attachment email_html_body.html.')
|
body = _('No plain-text email body available. Please see attachment "email_html_body.html".')
|
||||||
|
|
||||||
if body_html:
|
if body_html:
|
||||||
files.append({
|
files.append({
|
||||||
@ -246,7 +369,9 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
try:
|
try:
|
||||||
t = Ticket.objects.get(id=ticket)
|
t = Ticket.objects.get(id=ticket)
|
||||||
new = False
|
new = False
|
||||||
|
logger.info("Found existing ticket with Tracking ID %s-%s" % (t.queue.slug, t.id))
|
||||||
except Ticket.DoesNotExist:
|
except Ticket.DoesNotExist:
|
||||||
|
logger.info("Tracking ID %s-%s not associated with existing ticket. Creating new ticket." % (queue.slug, ticket))
|
||||||
ticket = None
|
ticket = None
|
||||||
|
|
||||||
priority = 3
|
priority = 3
|
||||||
@ -259,7 +384,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,18 +395,18 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
)
|
)
|
||||||
t.save()
|
t.save()
|
||||||
new = True
|
new = True
|
||||||
update = ''
|
logger.debug("Created new ticket %s-%s" % (t.queue.slug, t.id))
|
||||||
|
|
||||||
elif t.status == Ticket.CLOSED_STATUS:
|
elif t.status == Ticket.CLOSED_STATUS:
|
||||||
t.status = Ticket.REOPENED_STATUS
|
t.status = Ticket.REOPENED_STATUS
|
||||||
t.save()
|
t.save()
|
||||||
|
|
||||||
f = FollowUp(
|
f = FollowUp(
|
||||||
ticket = t,
|
ticket=t,
|
||||||
title = _('E-Mail Received from %(sender_email)s' % {'sender_email': sender_email}),
|
title=_('E-Mail Received from %(sender_email)s' % {'sender_email': sender_email}),
|
||||||
date = timezone.now(),
|
date=timezone.now(),
|
||||||
public = True,
|
public=True,
|
||||||
comment = body,
|
comment=body,
|
||||||
)
|
)
|
||||||
|
|
||||||
if t.status == Ticket.REOPENED_STATUS:
|
if t.status == Ticket.REOPENED_STATUS:
|
||||||
@ -289,25 +414,30 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
f.title = _('Ticket Re-Opened by E-Mail Received from %(sender_email)s' % {'sender_email': sender_email})
|
f.title = _('Ticket Re-Opened by E-Mail Received from %(sender_email)s' % {'sender_email': sender_email})
|
||||||
|
|
||||||
f.save()
|
f.save()
|
||||||
|
logger.debug("Created new FollowUp for Ticket")
|
||||||
|
|
||||||
if not quiet:
|
if six.PY2:
|
||||||
print((" [%s-%s] %s" % (t.queue.slug, t.id, t.title,)).encode('ascii', 'replace'))
|
logger.info(("[%s-%s] %s" % (t.queue.slug, t.id, t.title,)).encode('ascii', 'replace'))
|
||||||
|
elif six.PY3:
|
||||||
|
logger.info("[%s-%s] %s" % (t.queue.slug, t.id, t.title,))
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
if file['content']:
|
if file['content']:
|
||||||
filename = file['filename'].encode('ascii', 'replace').replace(' ', '_')
|
if six.PY2:
|
||||||
|
filename = file['filename'].encode('ascii', 'replace').replace(' ', '_')
|
||||||
|
elif six.PY3:
|
||||||
|
filename = file['filename'].replace(' ', '_')
|
||||||
filename = re.sub('[^a-zA-Z0-9._-]+', '', filename)
|
filename = re.sub('[^a-zA-Z0-9._-]+', '', filename)
|
||||||
|
logger.info("Found attachment '%s'" % filename)
|
||||||
a = Attachment(
|
a = Attachment(
|
||||||
followup=f,
|
followup=f,
|
||||||
filename=filename,
|
filename=filename,
|
||||||
mime_type=file['type'],
|
mime_type=file['type'],
|
||||||
size=len(file['content']),
|
size=len(file['content']),
|
||||||
)
|
)
|
||||||
a.file.save(filename, ContentFile(file['content']), save=False)
|
a.file.save(filename, ContentFile(file['content']), save=False)
|
||||||
a.save()
|
a.save()
|
||||||
if not quiet:
|
logger.info("Attachment '%s' successfully added to ticket." % filename)
|
||||||
print(" - %s" % filename)
|
|
||||||
|
|
||||||
|
|
||||||
context = safe_template_context(t)
|
context = safe_template_context(t)
|
||||||
|
|
||||||
@ -320,7 +450,7 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
recipients=sender_email,
|
recipients=sender_email,
|
||||||
sender=queue.from_address,
|
sender=queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if queue.new_ticket_cc:
|
if queue.new_ticket_cc:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
@ -329,7 +459,7 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
recipients=queue.new_ticket_cc,
|
recipients=queue.new_ticket_cc,
|
||||||
sender=queue.from_address,
|
sender=queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc:
|
if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
@ -338,15 +468,15 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
recipients=queue.updated_ticket_cc,
|
recipients=queue.updated_ticket_cc,
|
||||||
sender=queue.from_address,
|
sender=queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
context.update(comment=f.comment)
|
context.update(comment=f.comment)
|
||||||
|
|
||||||
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(
|
||||||
@ -355,7 +485,7 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
recipients=t.assigned_to.email,
|
recipients=t.assigned_to.email,
|
||||||
sender=queue.from_address,
|
sender=queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if queue.updated_ticket_cc:
|
if queue.updated_ticket_cc:
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
@ -364,11 +494,10 @@ def ticket_from_message(message, queue, quiet):
|
|||||||
recipients=queue.updated_ticket_cc,
|
recipients=queue.updated_ticket_cc,
|
||||||
sender=queue.from_address,
|
sender=queue.from_address,
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
return t
|
return t
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
process_email()
|
process_email()
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
35
helpdesk/migrations/0013_email_box_local_dir_and_logging.py
Normal file
35
helpdesk/migrations/0013_email_box_local_dir_and_logging.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.1 on 2016-09-14 23:47
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('helpdesk', '0012_queue_default_owner'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='queue',
|
||||||
|
name='email_box_local_dir',
|
||||||
|
field=models.CharField(blank=True, help_text='If using a local directory, what directory path do you wish to poll for new email? Example: /var/lib/mail/helpdesk/', max_length=200, null=True, verbose_name='E-Mail Local Directory'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='queue',
|
||||||
|
name='logging_dir',
|
||||||
|
field=models.CharField(blank=True, help_text='If logging is enabled, what directory should we use to store log files for this queue? If no directory is set, default to /var/log/helpdesk/', max_length=200, null=True, verbose_name='Logging Directory'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='queue',
|
||||||
|
name='logging_type',
|
||||||
|
field=models.CharField(blank=True, choices=[('none', 'None'), ('debug', 'Debug'), ('info', 'Information'), ('warn', 'Warning'), ('error', 'Error'), ('crit', 'Critical')], help_text='Set the default logging level. All messages at that level or above will be logged to the directory set below. If no level is set, logging will be disabled.', max_length=5, null=True, verbose_name='Logging Type'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='queue',
|
||||||
|
name='email_box_type',
|
||||||
|
field=models.CharField(blank=True, choices=[('pop3', 'POP 3'), ('imap', 'IMAP'), ('local', 'Local Directory')], help_text='E-Mail server type for creating tickets automatically from a mailbox - both POP3 and IMAP are supported, as well as reading from a local directory.', max_length=5, null=True, verbose_name='E-Mail Box Type'),
|
||||||
|
),
|
||||||
|
]
|
File diff suppressed because it is too large
Load Diff
@ -11,22 +11,26 @@ 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,
|
||||||
'email_on_ticket_change': True,
|
'email_on_ticket_change': True,
|
||||||
'login_view_ticketlist': True,
|
'login_view_ticketlist': True,
|
||||||
'email_on_ticket_apichange': True,
|
'tickets_per_page': 25
|
||||||
'tickets_per_page': 25
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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 +38,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 +60,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 +76,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,18 +105,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
|
||||||
|
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)
|
||||||
@ -101,6 +134,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)
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% trans "<a href='https://github.com/rossp/django-helpdesk'>django-helpdesk</a>." %}
|
{% trans "<a href='https://github.com/django-helpdesk/django-helpdesk'>django-helpdesk</a>." %}
|
||||||
|
@ -74,9 +74,9 @@
|
|||||||
<script src="{% static 'helpdesk/dist/js/sb-admin-2.js' %}"></script>
|
<script src="{% static 'helpdesk/dist/js/sb-admin-2.js' %}"></script>
|
||||||
|
|
||||||
<!-- RSS -->
|
<!-- RSS -->
|
||||||
<link rel='alternate' href='{% url 'helpdesk_rss_user' user.get_username %}' type='application/rss+xml' title='{% trans "My Open Tickets" %}' />
|
<link rel='alternate' href='{% url 'helpdesk:rss_user' user.get_username %}' type='application/rss+xml' title='{% trans "My Open Tickets" %}' />
|
||||||
<link rel='alternate' href='{% url 'helpdesk_rss_activity' %}' type='application/rss+xml' title='{% trans "All Recent Activity" %}' />
|
<link rel='alternate' href='{% url 'helpdesk:rss_activity' %}' type='application/rss+xml' title='{% trans "All Recent Activity" %}' />
|
||||||
<link rel='alternate' href='{% url 'helpdesk_rss_unassigned' %}' type='application/rss+xml' title='{% trans "Unassigned Tickets" %}' />
|
<link rel='alternate' href='{% url 'helpdesk:rss_unassigned' %}' type='application/rss+xml' title='{% trans "Unassigned Tickets" %}' />
|
||||||
|
|
||||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||||
@ -126,7 +126,6 @@
|
|||||||
|
|
||||||
<div id='footer' class="row">
|
<div id='footer' class="row">
|
||||||
<div class="col-md-2">{% include "helpdesk/attribution.html" %}</div>
|
<div class="col-md-2">{% include "helpdesk/attribution.html" %}</div>
|
||||||
<div class="col-md-2"><s><a href='{% url 'helpdesk_api_help' %}'>{% trans "API" %}</a></s></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include "helpdesk/debug.html" %}
|
{% include "helpdesk/debug.html" %}
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
<td>{{ ignore.date }}</td>
|
<td>{{ ignore.date }}</td>
|
||||||
<td>{% for queue in ignore.queues.all %}{{ queue.slug }}{% if not forloop.last %}, {% endif %}{% empty %}{% trans "All" %}{% endfor %}</td>
|
<td>{% for queue in ignore.queues.all %}{{ queue.slug }}{% if not forloop.last %}, {% endif %}{% empty %}{% trans "All" %}{% endfor %}</td>
|
||||||
<td>{% if ignore.keep_in_mailbox %}{% trans "Keep" %}{% endif %}</td>
|
<td>{% if ignore.keep_in_mailbox %}{% trans "Keep" %}{% endif %}</td>
|
||||||
<td><a href='{% url 'helpdesk_email_ignore_del' ignore.id %}'><button class="btn btn-danger btn-xs">{% trans "Delete" %}</button></a></td>
|
<td><a href='{% url 'helpdesk:email_ignore_del' ignore.id %}'><button class="btn btn-danger btn-xs">{% trans "Delete" %}</button></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -53,7 +53,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- /.row -->
|
<!-- /.row -->
|
||||||
|
|
||||||
|
|
||||||
<p>{% trans "<strong>Note:</strong> If the 'Keep' option is not selected, emails sent from that address will be deleted permanently." %}</p>
|
<p>{% trans "<strong>Note:</strong> If the 'Keep' option is not selected, emails sent from that address will be deleted permanently." %}</p>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,277 +0,0 @@
|
|||||||
{% extends "helpdesk/help_base.html" %}
|
|
||||||
|
|
||||||
{% block title %}django-helpdesk API Documentation{% endblock %}
|
|
||||||
{% block heading %}django-helpdesk API Documentation{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h2>Contents</h2>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href='#introduction'>Introduction</a></li>
|
|
||||||
<li><a href='#request'>Request Basics & Authentication</a></li>
|
|
||||||
<li><a href='#response'>Responses</a></li>
|
|
||||||
<li><a href='#methods'>Method Documentation</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href='#method_create_ticket'>create_ticket</a></li>
|
|
||||||
<li><a href='#method_delete_ticket'>delete_ticket</a></li>
|
|
||||||
<li><a href='#method_hold_ticket'>hold_ticket</a></li>
|
|
||||||
<li><a href='#method_unhold_ticket'>unhold_ticket</a></li>
|
|
||||||
<li><a href='#method_add_followup'>add_followup</a></li>
|
|
||||||
<li><a href='#method_resolve'>resolve</a></li>
|
|
||||||
<li><a href='#method_list_queues'>list_queues</a></li>
|
|
||||||
<li><a href='#method_find_user'>find_user</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<h2 id='warning'>Deprecation Warning</h2>
|
|
||||||
|
|
||||||
<P>This API has been deprecated and will be removed in January 2016. Please <a href='https://github.com/rossp/django-helpdesk/issues/198'>See the GitHub Issue Tracker</a> for more details.</p>
|
|
||||||
<P>Do <strong>not</strong> build new integrations using this API.</p>
|
|
||||||
|
|
||||||
<p>We recommend using django-rest-framework or similar for all integrations.</p>
|
|
||||||
|
|
||||||
<h2 id='introduction'>Introduction</h2>
|
|
||||||
|
|
||||||
<p>django-helpdesk provides a powerful <acronym title='Application Programming Interface'>API</acronym> to allow you to interact with your helpdesk tickets by a means not otherwise provided by the helpdesk.</p>
|
|
||||||
|
|
||||||
<p>For example, you may use this API to implement a system to automatically open a ticket when an invoice is raised in your invoicing system, or to automatically close a ticket from an instant messenger application.</p>
|
|
||||||
|
|
||||||
<p>Your use of this system is open-ended: most business cases should be addressible with a little bit of coding to allow you to interact nicely with your helpdesk.</p>
|
|
||||||
|
|
||||||
<h2 id='request'>Request Basics & Authentication</h2>
|
|
||||||
|
|
||||||
<p>All requests to the API must be made using <acroynm title='HyperText Transfer Protocol'>HTTP</acronym> POST requests. Any request that is not made using POST will raise an error.</p>
|
|
||||||
|
|
||||||
<p>Your requests must be made up of the following elements:</p>
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>A <em>method</em>, or action. This tells the API what core functionality to execute.</li>
|
|
||||||
<li>A <em>username</em> and <em>password</em> which are valid and active within your helpdesk system. You may wish to create a specific API user just for API usage.</li>
|
|
||||||
<li>A set of <em>data</em> to be saved into the database. This data will vary from request to request, and is outlined in <a href='#methods'>Methods</a> below.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<p>To build your request, send a HTTP POST request to <em>{% url 'helpdesk_api' "method" %}</em>, where <em>method</em> is the name of a <a href='#methods'>valid method</a> from the list below.</p>
|
|
||||||
|
|
||||||
<p>Your POST must include both <em>user</em> and <em>password</em> parameters.</p>
|
|
||||||
|
|
||||||
<p>A sample request for the method <em>hold_ticket</em> may look like this:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>A HTTP POST to <em>{% url 'helpdesk_api' "hold_ticket" %}</em></li>
|
|
||||||
<li>A set of POST data containing:<ul>
|
|
||||||
<li>username=susan</li>
|
|
||||||
<li>password=fido</li>
|
|
||||||
<li>ticket=31794</li>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>To complete this from a command-line using the <a href='http://curl.haxx.se/'>cURL</a> application, you may use a command such as this:</p>
|
|
||||||
|
|
||||||
<pre>/usr/bin/curl {% url 'helpdesk_api' "hold_ticket" %} --data "user=susan&password=fido&ticket=31794"</pre>
|
|
||||||
|
|
||||||
<p>In <a href='http://www.php.net/'>PHP</a>, providing you have access to the <a href='http://www.php.net/curl'>cURL libraries</a>, you may use code such as this:</p>
|
|
||||||
|
|
||||||
<pre><?php
|
|
||||||
$api = curl_init();
|
|
||||||
curl_setopt($api, CURLOPT_URL, "{% url 'helpdesk_api' "hold_ticket" %}");
|
|
||||||
curl_setopt($api, CURLOPT_POST, 1);
|
|
||||||
curl_setopt($api, CURLOPT_POSTFIELDS, "user=susan&password=fido&ticket=31794");
|
|
||||||
$result = curl_exec($api);
|
|
||||||
curl_close($api);
|
|
||||||
echo $result;
|
|
||||||
?></pre>
|
|
||||||
|
|
||||||
<p>Note that cURL expects all data to be urlencoded, this is left as an exercise for the reader.</p>
|
|
||||||
|
|
||||||
<h2 id='response'>Responses</h2>
|
|
||||||
|
|
||||||
<p>The API system makes proper use of the following HTTP response codes:</p>
|
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dt>200</dt>
|
|
||||||
<dd>OK - Data updated successfully</dd>
|
|
||||||
|
|
||||||
<dt>400</dt>
|
|
||||||
<dd>ERROR - Generic error. See returned text for details</dd>
|
|
||||||
|
|
||||||
<dt>404</dt>
|
|
||||||
<dd>ERROR - Data not found (eg, incorrect ticket). See returned text for details</dd>
|
|
||||||
|
|
||||||
<dt>403</dt>
|
|
||||||
<dd>ERROR - Invalid permissions (eg, incorrect username and/or password)</dd>
|
|
||||||
|
|
||||||
<dt>405</dt>
|
|
||||||
<dd>ERROR - Invalid method. You probably tried using GET, PUT or DELETE however we require POST.</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<p>Responses will have one of two content-types:</p>
|
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dt>text/plain</dt>
|
|
||||||
<dd>Any error messages, or simple responses (eg a ticket ID)</dd>
|
|
||||||
|
|
||||||
<dt>text/json</dt>
|
|
||||||
<dd>Any complex responses, such as a list of data.</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h2 id='methods'>Method Documentation</h2>
|
|
||||||
|
|
||||||
<p>The following public methods are available for use via the API. Each of them requires <a href='#request'>a valid request and authentication</a>, and each has it's own parameters as described below.</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href='#method_create_ticket'>create_ticket</a></li>
|
|
||||||
<li><a href='#method_delete_ticket'>delete_ticket</a></li>
|
|
||||||
<li><a href='#method_hold_ticket'>hold_ticket</a></li>
|
|
||||||
<li><a href='#method_unhold_ticket'>unhold_ticket</a></li>
|
|
||||||
<li><a href='#method_add_followup'>add_followup</a></li>
|
|
||||||
<li><a href='#method_resolve'>resolve</a></li>
|
|
||||||
<li><a href='#method_list_queues'>list_queues</a></li>
|
|
||||||
<li><a href='#method_find_user'>find_user</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id='method_create_ticket'>create_ticket</h3>
|
|
||||||
|
|
||||||
<p>This method creates a new helpdesk ticket.</p>
|
|
||||||
|
|
||||||
<h4>Parameters</h4>
|
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dt>queue</dt>
|
|
||||||
<dd>Queue ID (use <a href='#method_list_queues'>list_queues</a> to get queue ID's) - this is an integer field.</dd>
|
|
||||||
|
|
||||||
<dt>title</dt>
|
|
||||||
<dd>Title or header of this ticket. Character field, maximum 100 characters.</dd>
|
|
||||||
|
|
||||||
<dt>submitter_email</dt>
|
|
||||||
<dd>(Optional) e-mail address of the person submitting this ticket. This e-mail address will receive copies of all public updates to this ticket, and will receive a notification when the ticket is created.</dd>
|
|
||||||
|
|
||||||
<dt>assigned_to</dt>
|
|
||||||
<dd>(Optional) Integer ID of the user to which this ticket should be assigned. Use <a href='#method_find_user'>find_user</a> to find a user ID from a username.</dd>
|
|
||||||
|
|
||||||
<dt>priority</dt>
|
|
||||||
<dd>(Optional) Priority as an integer from 1 (high) to 5 (low). Defaults to 3 if no priority given.</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h4>Response</h4>
|
|
||||||
|
|
||||||
<p>This method responds with <strong>plain-text</strong>.</p>
|
|
||||||
|
|
||||||
<p>If you receive a 200 OK <a href='#response'>response</a>, then the content of the response will be the ticket ID.</p>
|
|
||||||
|
|
||||||
<h3 id='method_delete_ticket'>delete_ticket</h3>
|
|
||||||
|
|
||||||
<p>When given a ticket ID and confirmation, this method will delete a ticket entirely. This also deletes any followups, attachments, and other details.</p>
|
|
||||||
|
|
||||||
<h4>Parameters</h4>
|
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dt>ticket</dt>
|
|
||||||
<dd>The numeric ticket ID to be deleted</dd>
|
|
||||||
|
|
||||||
<dt>confirm</dt>
|
|
||||||
<dd>You must provide this field, with any value, to enable deletion to continue</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h4>Response</h4>
|
|
||||||
|
|
||||||
<p>A standard <a href='#response'>200 OK response</a> is given on success, or an error message on failure.</p>
|
|
||||||
|
|
||||||
<h3 id='method_hold_ticket'>hold_ticket</h3>
|
|
||||||
|
|
||||||
<p>If a ticket needs to be placed on hold, preventing it from being escalated, use this method.</p>
|
|
||||||
|
|
||||||
<h4>Parameters</h4>
|
|
||||||
<dl>
|
|
||||||
<dt>ticket</dt>
|
|
||||||
<dd>The numeric ticket ID to be placed on hold</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h4>Response</h4>
|
|
||||||
|
|
||||||
<p>A standard <a href='#response'>200 OK response</a> is given on success, or an error message on failure.</p>
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id='method_unhold_ticket'>unhold_ticket</h3>
|
|
||||||
|
|
||||||
<p>If a ticket is currently on hold and you wish to remove that hold, use this method.</p>
|
|
||||||
|
|
||||||
<h4>Parameters</h4>
|
|
||||||
<dl>
|
|
||||||
<dt>ticket</dt>
|
|
||||||
<dd>The numeric ticket ID to be taken off hold</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h4>Response</h4>
|
|
||||||
|
|
||||||
<p>A standard <a href='#response'>200 OK response</a> is given on success, or an error message on failure.</p>
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id='method_add_followup'>add_followup</h3>
|
|
||||||
|
|
||||||
<p>This method adds a comment / followup to a ticket. The followup can be public, in which case it is e-mailed to the submitter, or private. The followup will also be sent to others involved in the ticket: The owner and the queue notification / CC address.</p>
|
|
||||||
|
|
||||||
<h4>Parameters</h4>
|
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dt>ticket</dt>
|
|
||||||
<dd>The numeric ticket ID to which this followup should be added</dd>
|
|
||||||
|
|
||||||
<dt>message</dt>
|
|
||||||
<dd>Text of 'unlimited' length - optionally formatted with HTML - to add to the message.</dd>
|
|
||||||
|
|
||||||
<dt>public</dt>
|
|
||||||
<dd>Either 'y' for public, or 'n' for private. This is optional, and it is assumed that followups are private if it is not provided. Private tickets are <strong>not</strong> e-mailed to the ticket submitter.</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h4>Response</h4>
|
|
||||||
|
|
||||||
<p>A standard <a href='#response'>200 OK response</a> is given on success, or an error message on failure.</p>
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id='method_resolve'>resolve</h3>
|
|
||||||
|
|
||||||
<p>This method adds a resolution to a ticket and marks it as resolved. The resolution will be e-mailed to everybody involved with the ticket, including the submitter.</p>
|
|
||||||
|
|
||||||
<h4>Parameters</h4>
|
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dt>ticket</dt>
|
|
||||||
<dd>The numeric ticket ID to which this followup should be added</dd>
|
|
||||||
|
|
||||||
<dt>resolution</dt>
|
|
||||||
<dd>Text of 'unlimited' length - optionally formatted with HTML. This is the resolution for this ticket.</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h4>Response</h4>
|
|
||||||
|
|
||||||
<p>A standard <a href='#response'>200 OK response</a> is given on success, or an error message on failure.</p>
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id='method_list_queues'>list_queues</h3>
|
|
||||||
|
|
||||||
<p>This method provides a JSON-parsable list of queues, letting you access the individual queue ID in order to create tickets.</p>
|
|
||||||
|
|
||||||
<h4>Response</h4>
|
|
||||||
|
|
||||||
<p>This method responds with <strong>json</strong>.</p>
|
|
||||||
|
|
||||||
<p>It provides a list of queues in JSON format. The fields provided are ID and Title.</p>
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id='method_find_user'>find_user</h3>
|
|
||||||
|
|
||||||
<p>When given a username, this method provides the related numeric user ID - commonly used when creating or reassigning tickets.</p>
|
|
||||||
|
|
||||||
<h4>Parameters</h4>
|
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dt>username</dt>
|
|
||||||
<dd>The case-sensitive username of the user for which you require the user ID</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h4>Response</h4>
|
|
||||||
|
|
||||||
<p>This method responds with <strong>plain-text</strong>.</p>
|
|
||||||
|
|
||||||
<p>If you receive a 200 OK <a href='#response'>response</a>, then the content of the response will be the users ID.</p>
|
|
||||||
{% endblock %}
|
|
@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if entry.1 > 0 %}<a href="{% url 'helpdesk_list' %}?{{ entry.3 }}">{% else %}<a href="#">{% endif %}
|
{% if entry.1 > 0 %}<a href="{% url 'helpdesk:list' %}?{{ entry.3 }}">{% else %}<a href="#">{% endif %}
|
||||||
<div class="panel-footer">
|
<div class="panel-footer">
|
||||||
<span class="pull-left">{% if entry.1 > 0 %}{% trans "View Tickets" %}{% else %}{% trans "No tickets in this range" %}{% endif %}</span>
|
<span class="pull-left">{% if entry.1 > 0 %}{% trans "View Tickets" %}{% else %}{% trans "No tickets in this range" %}{% endif %}</span>
|
||||||
{% if entry.1 > 0 %}<span class="pull-right"><i class="fa fa-arrow-circle-right"></i></span>{% endif %}
|
{% if entry.1 > 0 %}<span class="pull-right"><i class="fa fa-arrow-circle-right"></i></span>{% endif %}
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
<td><span title='{{ ticket.created|date:"r" }}'>{{ ticket.created|naturaltime }}</span></td>
|
<td><span title='{{ ticket.created|date:"r" }}'>{{ ticket.created|naturaltime }}</span></td>
|
||||||
<td>
|
<td>
|
||||||
<a href='{{ ticket.get_absolute_url }}?take'><button class='btn btn-primary btn-xs'><i class="fa fa-hand-paper-o"></i> {% trans "Take" %}</button></a> |
|
<a href='{{ ticket.get_absolute_url }}?take'><button class='btn btn-primary btn-xs'><i class="fa fa-hand-paper-o"></i> {% trans "Take" %}</button></a> |
|
||||||
<a href='{% url 'helpdesk_delete' ticket.id %}'><button class='btn btn-danger btn-xs'><i class="fa fa-trash"></i> {% trans "Delete" %}</button></a>
|
<a href='{% url 'helpdesk:delete' ticket.id %}'><button class='btn btn-danger btn-xs'><i class="fa fa-trash"></i> {% trans "Delete" %}</button></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
</button>
|
</button>
|
||||||
<a class="navbar-brand" href="{% url 'helpdesk_home' %}">Helpdesk</a>
|
<a class="navbar-brand" href="{% url 'helpdesk:home' %}">Helpdesk</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- /.navbar-header -->
|
<!-- /.navbar-header -->
|
||||||
|
|
||||||
@ -17,27 +17,28 @@
|
|||||||
{% if helpdesk_settings.HELPDESK_NAVIGATION_ENABLED and user.is_authenticated or user.is_staff %}
|
{% if helpdesk_settings.HELPDESK_NAVIGATION_ENABLED and user.is_authenticated or user.is_staff %}
|
||||||
<ul class="nav navbar-top-links navbar-right">
|
<ul class="nav navbar-top-links navbar-right">
|
||||||
<li>
|
<li>
|
||||||
<a href='{% url 'helpdesk_dashboard' %}'><i class="fa fa-dashboard fa-fw"></i> <span class="nav-text">{% trans "Dashboard" %}</span></a>
|
<a href='{% url 'helpdesk:dashboard' %}'><i class="fa fa-dashboard fa-fw"></i> <span class="nav-text">{% trans "Dashboard" %}</span></a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href='{% url 'helpdesk_list' %}'><i class="fa fa-tasks fa-fw"></i> <span class="nav-text">{% trans "Tickets" %}</span></a>
|
<a href='{% url 'helpdesk:list' %}'><i class="fa fa-tasks fa-fw"></i> <span class="nav-text">{% trans "Tickets" %}</span></a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href='{% url 'helpdesk_submit' %}'><i class="fa fa-plus-circle fa-fw"></i> <span class="nav-text">{% trans "New Ticket" %}</span></a>
|
<a href='{% url 'helpdesk:submit' %}'><i class="fa fa-plus-circle fa-fw"></i> <span class="nav-text">{% trans "New Ticket" %}</span></a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href='{% url 'helpdesk_report_index' %}'><i class="fa fa-bar-chart-o fa-fw"></i> <span class="nav-text"> {% trans "Stats" %}</span></a>
|
<a href='{% url 'helpdesk:report_index' %}'><i class="fa fa-bar-chart-o fa-fw"></i> <span class="nav-text"> {% trans "Stats" %}</span></a>
|
||||||
</li>
|
</li>
|
||||||
{% if helpdesk_settings.HELPDESK_KB_ENABLED %}
|
{% if helpdesk_settings.HELPDESK_KB_ENABLED %}
|
||||||
<li>
|
<li>
|
||||||
<a href='{% url 'helpdesk_kb_index' %}'><i class="fa fa-database fa-fw"></i> <span class="nav-text">{% trans "Knowledgebase" %}</span></a>
|
<a href='{% url 'helpdesk:kb_index' %}'><i class="fa fa-database fa-fw"></i> <span class="nav-text">{% trans "Knowledgebase" %}</span></a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user_saved_queries_ %}
|
{% if user_saved_queries_ %}
|
||||||
<li class="headerlink dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#"><i class="fa fa-filter fa-fw"></i> <span class="nav-text">{% trans "Saved Query" %} <b class="caret"></b></span></a>
|
<li class="headerlink dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#"><i class="fa fa-filter fa-fw"></i> <span class="nav-text">{% trans "Saved Query" %} <b class="caret"></b></span></a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
{% for q in user_saved_queries_ %}
|
{% for q in user_saved_queries_ %}
|
||||||
<li><a href="{% url 'helpdesk_list' %}?saved_query={{ q.id }}">{{ q.title }}
|
<li><a href="{% url 'helpdesk:list' %}?saved_query={{ q.id }}">{{ q.title }}
|
||||||
{% if q.shared %}
|
{% if q.shared %}
|
||||||
(Shared{% ifnotequal user q.user %} by {{ q.user.get_username }}{% endifnotequal %})
|
(Shared{% ifnotequal user q.user %} by {{ q.user.get_username }}{% endifnotequal %})
|
||||||
{% endif %}</a></li>
|
{% endif %}</a></li>
|
||||||
@ -47,7 +48,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not query %}
|
{% if not query %}
|
||||||
<li>
|
<li>
|
||||||
<form class="navbar-form navbar-top" id='searchform' method='get' action="{% url 'helpdesk_list' %}">
|
<form class="navbar-form navbar-top" id='searchform' method='get' action="{% url 'helpdesk:list' %}">
|
||||||
<div class="input-group custom-search-form">
|
<div class="input-group custom-search-form">
|
||||||
<input type='text' name='q' size='15' class='input form-control' placeholder='{% trans "Search..." %}' id='search_query' title='{% trans "Enter a keyword, or a ticket number to jump straight to that ticket." %}'/>
|
<input type='text' name='q' size='15' class='input form-control' placeholder='{% trans "Search..." %}' id='search_query' title='{% trans "Enter a keyword, or a ticket number to jump straight to that ticket." %}'/>
|
||||||
<input type='hidden' name='status' value='1' /><input type='hidden' name='status' value='2' /><input type='hidden' name='status' value='3' /><input type='hidden' name='search_type' value='header' />
|
<input type='hidden' name='status' value='1' /><input type='hidden' name='status' value='2' /><input type='hidden' name='status' value='3' /><input type='hidden' name='search_type' value='header' />
|
||||||
@ -67,15 +68,15 @@
|
|||||||
<i class="fa fa-user fa-fw"></i> {{ user.get_full_name|default:user.get_username }} <i class="fa fa-caret-down"></i>
|
<i class="fa fa-user fa-fw"></i> {{ user.get_full_name|default:user.get_username }} <i class="fa fa-caret-down"></i>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-user">
|
<ul class="dropdown-menu dropdown-user">
|
||||||
<li><a href="{% url 'helpdesk_user_settings' %}"><i class="fa fa-gear fa-fw"></i> {% trans "User Settings" %}</a>
|
<li><a href="{% url 'helpdesk:user_settings' %}"><i class="fa fa-gear fa-fw"></i> {% trans "User Settings" %}</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a href='{% url 'helpdesk_rss_index' %}'><i class="fa fa-rss-square fa-fw"></i> {% trans "RSS Feeds" %}</a></li>
|
<li><a href='{% url 'helpdesk:rss_index' %}'><i class="fa fa-rss-square fa-fw"></i> {% trans "RSS Feeds" %}</a></li>
|
||||||
{% if helpdesk_settings.HELPDESK_SHOW_CHANGE_PASSWORD and user.has_usable_password %}
|
{% if helpdesk_settings.HELPDESK_SHOW_CHANGE_PASSWORD and user.has_usable_password %}
|
||||||
<li><a href="{% url 'auth_password_change' %}"><i class="fa fa-user-secret fa-fw"></i> {% trans "Change password" %}</a></li>
|
<li><a href="{% url 'auth_password_change' %}"><i class="fa fa-user-secret fa-fw"></i> {% trans "Change password" %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
{% if user.is_superuser %}
|
{% if user.is_superuser %}
|
||||||
<li><a href='{% url 'helpdesk_system_settings' %}'><i class="fa fa-gears fa-fw"></i> {% trans "System Settings" %}</a></li>
|
<li><a href='{% url 'helpdesk:system_settings' %}'><i class="fa fa-gears fa-fw"></i> {% trans "System Settings" %}</a></li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li><a href="{% url 'logout' %}"><i class="fa fa-sign-out fa-fw"></i> {% trans "Logout" %}</a>
|
<li><a href="{% url 'logout' %}"><i class="fa fa-sign-out fa-fw"></i> {% trans "Logout" %}</a>
|
||||||
@ -90,16 +91,16 @@
|
|||||||
{# Public menu #}
|
{# Public menu #}
|
||||||
<ul id="dropdown" class="nav navbar-nav">
|
<ul id="dropdown" class="nav navbar-nav">
|
||||||
{% if helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE %}
|
{% if helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE %}
|
||||||
<li><a href='{% url 'helpdesk_dashboard' %}'>{% trans "Dashboard" %}</a></li>
|
<li><a href='{% url 'helpdesk:dashboard' %}'>{% trans "Dashboard" %}</a></li>
|
||||||
<li><a href='{% url 'helpdesk_submit' %}'>{% trans "Submit a Ticket" %}</a></li>
|
<li><a href='{% url 'helpdesk:submit' %}'>{% trans "Submit a Ticket" %}</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if helpdesk_settings.HELPDESK_SUBMIT_A_TICKET_PUBLIC %}
|
{% if helpdesk_settings.HELPDESK_SUBMIT_A_TICKET_PUBLIC %}
|
||||||
<li><a href='{% url 'helpdesk_submit' %}'>{% trans "Submit a Ticket" %}</a></li>
|
<li><a href='{% url 'helpdesk:submit' %}'>{% trans "Submit a Ticket" %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if helpdesk_settings.HELPDESK_KB_ENABLED %}<li><a href='{% url 'helpdesk_kb_index' %}'>{% trans "Knowledgebase" %}</a></li>{% endif %}
|
{% if helpdesk_settings.HELPDESK_KB_ENABLED %}<li><a href='{% url 'helpdesk:kb_index' %}'>{% trans "Knowledgebase" %}</a></li>{% endif %}
|
||||||
{% if not request.path == '/helpdesk/login/' or user.is_authenticated %}
|
{% if not request.path == '/helpdesk/login/' or user.is_authenticated %}
|
||||||
<li>{% if user.is_authenticated %}<a href='{% url 'logout' %}'>{% trans "Logout" %}</a>{% else %}<a href='{% url 'login' %}?next={% if next %}{{ next|escape }}{% else %}{% url 'helpdesk_home' %}{% endif %}'>{% trans "Log In" %}</a>{% endif %}</li>
|
<li>{% if user.is_authenticated %}<a href='{% url 'logout' %}'>{% trans "Logout" %}</a>{% else %}<a href='{% url 'login' %}?next={% if next %}{{ next|escape }}{% else %}{% url 'helpdesk:home' %}{% endif %}'>{% trans "Log In" %}</a>{% endif %}</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -72,7 +72,7 @@
|
|||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<h2>{% trans "View a Ticket" %}</h2>
|
<h2>{% trans "View a Ticket" %}</h2>
|
||||||
|
|
||||||
<form method='get' action="{% url 'helpdesk_public_view' %}">
|
<form method='get' action="{% url 'helpdesk:public_view' %}">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
||||||
<label for='id_ticket'>{% trans "Ticket" %}</label>
|
<label for='id_ticket'>{% trans "Ticket" %}</label>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
{% block helpdesk_body %}
|
{% block helpdesk_body %}
|
||||||
<h2>{% trans "View a Ticket" %}</h2>
|
<h2>{% trans "View a Ticket" %}</h2>
|
||||||
|
|
||||||
<form method='get' action='{% url 'helpdesk_public_view' %}'>
|
<form method='get' action='{% url 'helpdesk:public_view' %}'>
|
||||||
|
|
||||||
{% if error_message %}<p><strong>{% trans "Error:" %}</strong> {{ error_message }}</p>{% endif %}
|
{% if error_message %}<p><strong>{% trans "Error:" %}</strong> {{ error_message }}</p>{% endif %}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
{% block helpdesk_body %}
|
{% block helpdesk_body %}
|
||||||
|
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<meta http-equiv="REFRESH" content="0;url={% url 'helpdesk_home' %}">
|
<meta http-equiv="REFRESH" content="0;url={% url 'helpdesk:home' %}">
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
<div class="col-md-4 col-md-offset-4">
|
<div class="col-md-4 col-md-offset-4">
|
||||||
|
@ -7,19 +7,18 @@
|
|||||||
<p>{% trans "The following RSS feeds are available for you to monitor using your preferred RSS software. With the exception of the 'Latest Activity' feed, all feeds provide information only on Open and Reopened cases. This ensures your RSS reader isn't full of information about closed or historical tasks." %}</p>
|
<p>{% trans "The following RSS feeds are available for you to monitor using your preferred RSS software. With the exception of the 'Latest Activity' feed, all feeds provide information only on Open and Reopened cases. This ensures your RSS reader isn't full of information about closed or historical tasks." %}</p>
|
||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
<dt><a href='{% url 'helpdesk_rss_user' user.get_username %}'><i class="fa fa-rss-square fa-fw"></i>{% trans "My Open Tickets" %}</a></dt>
|
<dt><a href='{% url 'helpdesk:rss_user' user.get_username %}'><i class="fa fa-rss-square fa-fw"></i>{% trans "My Open Tickets" %}</a></dt>
|
||||||
<dd>{% trans "A summary of your open tickets - useful for getting alerted to new tickets opened for you" %}</dd>
|
<dd>{% trans "A summary of your open tickets - useful for getting alerted to new tickets opened for you" %}</dd>
|
||||||
|
|
||||||
<dt><a href='{% url 'helpdesk_rss_activity' %}'><i class="fa fa-rss-square fa-fw"></i>{% trans "Latest Activity" %}</a></dt>
|
<dt><a href='{% url 'helpdesk:rss_activity' %}'><i class="fa fa-rss-square fa-fw"></i>{% trans "Latest Activity" %}</a></dt>
|
||||||
<dd>{% trans "A summary of all helpdesk activity - including comments, emails, attachments, and more" %}</dd>
|
<dd>{% trans "A summary of all helpdesk activity - including comments, emails, attachments, and more" %}</dd>
|
||||||
|
|
||||||
<dt><a href='{% url 'helpdesk_rss_unassigned' %}'><i class="fa fa-rss-square fa-fw"></i>{% trans "Unassigned Tickets" %}</a></dt>
|
<dt><a href='{% url 'helpdesk:rss_unassigned' %}'><i class="fa fa-rss-square fa-fw"></i>{% trans "Unassigned Tickets" %}</a></dt>
|
||||||
<dd>{% trans "All unassigned tickets - useful for being alerted to new tickets opened by the public via the web or via e-mail" %}</dd>
|
<dd>{% trans "All unassigned tickets - useful for being alerted to new tickets opened by the public via the web or via e-mail" %}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<p>{% trans "These RSS feeds allow you to view a summary of either your own tickets, or all tickets, for each of the queues in your helpdesk. For example, if you manage the staff who utilise a particular queue, this may be used to view new tickets coming into that queue." %}</p>
|
<p>{% trans "These RSS feeds allow you to view a summary of either your own tickets, or all tickets, for each of the queues in your helpdesk. For example, if you manage the staff who utilise a particular queue, this may be used to view new tickets coming into that queue." %}</p>
|
||||||
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<div class="panel panel-primary">
|
<div class="panel panel-primary">
|
||||||
@ -41,8 +40,8 @@
|
|||||||
{% for queue in queues %}
|
{% for queue in queues %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ queue.title }}</td>
|
<td>{{ queue.title }}</td>
|
||||||
<td align='center'><a href='{% url 'helpdesk_rss_queue' queue.slug %}'><i class="fa fa-rss-square fa-fw"></i></a></td>
|
<td align='center'><a href='{% url 'helpdesk:rss_queue' queue.slug %}'><i class="fa fa-rss-square fa-fw"></i></a></td>
|
||||||
<td align='center'><a href='{% url 'helpdesk_rss_user_queue' user.get_username queue.slug %}'><i class="fa fa-rss-square fa-fw"></i></a></td>
|
<td align='center'><a href='{% url 'helpdesk:rss_user_queue' user.get_username queue.slug %}'><i class="fa fa-rss-square fa-fw"></i></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<p>{% blocktrans %}The following items can be maintained by you or other superusers:{% endblocktrans %}</p>
|
<p>{% blocktrans %}The following items can be maintained by you or other superusers:{% endblocktrans %}</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href='{% url 'helpdesk_email_ignore' %}'>{% trans "E-Mail Ignore list" %}</a></li>
|
<li><a href='{% url 'helpdesk:email_ignore' %}'>{% trans "E-Mail Ignore list" %}</a></li>
|
||||||
<li><a href='{% url 'admin:helpdesk_queue_changelist' %}'>{% trans "Maintain Queues" %}</a></li>
|
<li><a href='{% url 'admin:helpdesk_queue_changelist' %}'>{% trans "Maintain Queues" %}</a></li>
|
||||||
<li><a href='{% url 'admin:helpdesk_presetreply_changelist' %}'>{% trans "Maintain Pre-Set Replies" %}</a></li>
|
<li><a href='{% url 'admin:helpdesk_presetreply_changelist' %}'>{% trans "Maintain Pre-Set Replies" %}</a></li>
|
||||||
<li><a href='{% url 'admin:helpdesk_kbcategory_changelist' %}'>{% trans "Maintain Knowledgebase Categories" %}</a></li>
|
<li><a href='{% url 'admin:helpdesk_kbcategory_changelist' %}'>{% trans "Maintain Knowledgebase Categories" %}</a></li>
|
||||||
|
@ -20,7 +20,7 @@ $(document).ready(function() {
|
|||||||
$('#id_preset').change(function() {
|
$('#id_preset').change(function() {
|
||||||
preset = $('#id_preset').val();
|
preset = $('#id_preset').val();
|
||||||
if (preset != '') {
|
if (preset != '') {
|
||||||
$.get("{% url 'helpdesk_raw' "preset" %}?id=" + preset, function(data) {
|
$.get("{% url 'helpdesk:raw' "preset" %}?id=" + preset, function(data) {
|
||||||
$("#commentBox").val(data)
|
$("#commentBox").val(data)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -100,7 +100,7 @@ $(document).on('change', ':file', function() {
|
|||||||
{% for attachment in followup.attachment_set.all %}{% if forloop.first %}<hr><div class='attachments'><ul>{% endif %}
|
{% for attachment in followup.attachment_set.all %}{% if forloop.first %}<hr><div class='attachments'><ul>{% endif %}
|
||||||
<li><a href='{{ attachment.file.url }}'>{{ attachment.filename }}</a> ({{ attachment.mime_type }}, {{ attachment.size|filesizeformat }})
|
<li><a href='{{ attachment.file.url }}'>{{ attachment.filename }}</a> ({{ attachment.mime_type }}, {{ attachment.size|filesizeformat }})
|
||||||
{% if followup.user and request.user == followup.user %}
|
{% if followup.user and request.user == followup.user %}
|
||||||
<a href='{% url 'helpdesk_attachment_del' ticket.id attachment.id %}'><button class="btn btn-danger btn-xs"><i class="fa fa-trash"></i> {% trans 'Delete' %}</button></a>
|
<a href='{% url 'helpdesk:attachment_del' ticket.id attachment.id %}'><button class="btn btn-danger btn-xs"><i class="fa fa-trash"></i> {% trans 'Delete' %}</button></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% if forloop.last %}</ul></div>{% endif %}
|
{% if forloop.last %}</ul></div>{% endif %}
|
||||||
@ -109,11 +109,11 @@ $(document).on('change', ':file', function() {
|
|||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
{% if helpdesk_settings.HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP %}
|
{% if helpdesk_settings.HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP %}
|
||||||
{% if followup.user and request.user == followup.user and not followup.ticketchange_set.all %}
|
{% if followup.user and request.user == followup.user and not followup.ticketchange_set.all %}
|
||||||
<a href="{% url 'helpdesk_followup_edit' ticket.id followup.id %}" class='followup-edit'><button type="button" class="btn btn-warning btn-xs"><i class="fa fa-edit"></i> {% trans "Edit" %}</button></a>
|
<a href="{% url 'helpdesk:followup_edit' ticket.id followup.id %}" class='followup-edit'><button type="button" class="btn btn-warning btn-xs"><i class="fa fa-edit"></i> {% trans "Edit" %}</button></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.is_superuser and helpdesk_settings.HELPDESK_SHOW_DELETE_BUTTON_SUPERUSER_FOLLOW_UP %}
|
{% if user.is_superuser and helpdesk_settings.HELPDESK_SHOW_DELETE_BUTTON_SUPERUSER_FOLLOW_UP %}
|
||||||
<a href="{% url 'helpdesk_followup_delete' ticket.id followup.id %}" class='followup-edit'><button type="button" class="btn btn-warning btn-xs"><i class="fa fa-trash"></i> {% trans "Delete" %}</button></a>
|
<a href="{% url 'helpdesk:followup_delete' ticket.id followup.id %}" class='followup-edit'><button type="button" class="btn btn-warning btn-xs"><i class="fa fa-trash"></i> {% trans "Delete" %}</button></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,8 +9,6 @@
|
|||||||
|
|
||||||
<p>You can add a new recipient to the list or delete any of the items below as required.</p>{% endblocktrans %}
|
<p>You can add a new recipient to the list or delete any of the items below as required.</p>{% endblocktrans %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<div class="panel panel-primary">
|
<div class="panel panel-primary">
|
||||||
@ -36,7 +34,7 @@
|
|||||||
<td>{{ person.display }}</td>
|
<td>{{ person.display }}</td>
|
||||||
<td>{{ person.can_view }}</td>
|
<td>{{ person.can_view }}</td>
|
||||||
<td>{{ person.can_update }}</td>
|
<td>{{ person.can_update }}</td>
|
||||||
<td><a href='{% url 'helpdesk_ticket_cc_del' ticket.id person.id %}'><button class="btn btn-danger btn-xs">{% trans "Delete" %}</button></a></td>
|
<td><a href='{% url 'helpdesk:ticket_cc_del' ticket.id person.id %}'><button class="btn btn-danger btn-xs">{% trans "Delete" %}</button></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -52,6 +50,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- /.row -->
|
<!-- /.row -->
|
||||||
|
|
||||||
<p><a href='{% url 'helpdesk_view' ticket.id %}'><button class="btn btn-primary btn-lg">{% blocktrans with ticket.title as ticket_title %}Return to <em>{{ ticket_title }}</em>{% endblocktrans %}</button></a></p>
|
<p><a href='{% url 'helpdesk:view' ticket.id %}'><button class="btn btn-primary btn-lg">{% blocktrans with ticket.title as ticket_title %}Return to <em>{{ ticket_title }}</em>{% endblocktrans %}</button></a></p>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -13,9 +13,9 @@
|
|||||||
<table class="table table-striped table-bordered table-hover">
|
<table class="table table-striped table-bordered table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class='row_tablehead'><td colspan='2'><h3>{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}]</h3> <span class='ticket_toolbar'>
|
<tr class='row_tablehead'><td colspan='2'><h3>{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}]</h3> <span class='ticket_toolbar'>
|
||||||
<a href="{% url 'helpdesk_edit' ticket.id %}" class="ticket-edit"><button class="btn btn-warning btn-xs"><i class="fa fa-pencil"></i> {% trans "Edit" %}</button></a>
|
<a href="{% url 'helpdesk:edit' ticket.id %}" class="ticket-edit"><button class="btn btn-warning btn-xs"><i class="fa fa-pencil"></i> {% trans "Edit" %}</button></a>
|
||||||
| <a href="{% url 'helpdesk_delete' ticket.id %}" class="ticket-delete"><button class="btn btn-danger btn-xs"><i class="fa fa-trash-o"></i> {% trans "Delete" %}</button></a>
|
| <a href="{% url 'helpdesk:delete' ticket.id %}" class="ticket-delete"><button class="btn btn-danger btn-xs"><i class="fa fa-trash-o"></i> {% trans "Delete" %}</button></a>
|
||||||
{% if ticket.on_hold %} | <a href="{% url 'helpdesk_unhold' ticket.id %}" class="ticket-hold"><button class="btn btn-warning btn-xs"><i class="fa fa-play"></i> {% trans "Unhold" %}</button></a>{% else %} | <a href="{% url 'helpdesk_hold' ticket.id %}" class="ticket-hold"><button class="btn btn-warning btn-xs"><i class="fa fa-pause"></i> {% trans "Hold" %}</button></a>{% endif %}
|
{% if ticket.on_hold %} | <a href="{% url 'helpdesk:unhold' ticket.id %}" class="ticket-hold"><button class="btn btn-warning btn-xs"><i class="fa fa-play"></i> {% trans "Unhold" %}</button></a>{% else %} | <a href="{% url 'helpdesk:hold' ticket.id %}" class="ticket-hold"><button class="btn btn-warning btn-xs"><i class="fa fa-pause"></i> {% trans "Hold" %}</button></a>{% endif %}
|
||||||
</span></td></tr>
|
</span></td></tr>
|
||||||
<tr><th colspan='2'>{% blocktrans with ticket.queue as queue %}Queue: {{ queue }}{% endblocktrans %}</th></tr>
|
<tr><th colspan='2'>{% blocktrans with ticket.queue as queue %}Queue: {{ queue }}{% endblocktrans %}</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -50,7 +50,7 @@
|
|||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Submitter E-Mail" %}</th>
|
<th>{% trans "Submitter E-Mail" %}</th>
|
||||||
<td>{{ ticket.submitter_email }}{% if user.is_superuser %} <strong><a href='{% url 'helpdesk_email_ignore_add' %}?email={{ ticket.submitter_email }}'><button type="button" class="btn btn-warning btn-xs"><i class="fa fa-eye-slash"></i> {% trans "Ignore" %}</button></a></strong>{% endif %}</td>
|
<td>{{ ticket.submitter_email }}{% if user.is_superuser %} <strong><a href='{% url 'helpdesk:email_ignore_add' %}?email={{ ticket.submitter_email }}'><button type="button" class="btn btn-warning btn-xs"><i class="fa fa-eye-slash"></i> {% trans "Ignore" %}</button></a></strong>{% endif %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
@ -60,19 +60,19 @@
|
|||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Copies To" %}</th>
|
<th>{% trans "Copies To" %}</th>
|
||||||
<td>{{ ticketcc_string }} <a data-toggle='tooltip' href='{% url 'helpdesk_ticket_cc' ticket.id %}' title='{% trans "Click here to add / remove people who should receive an e-mail whenever this ticket is updated." %}'><strong><button type="button" class="btn btn-warning btn-xs"><i class="fa fa-share"></i> {% trans "Manage" %}</button></strong></a>{% if SHOW_SUBSCRIBE %}, <strong><a data-toggle='tooltip' href='?subscribe' title='{% trans "Click here to subscribe yourself to this ticket, if you want to receive an e-mail whenever this ticket is updated." %}'><button type="button" class="btn btn-warning btn-xs"><i class="fa fa-rss"></i> {% trans "Subscribe" %}</button></a></strong>{% endif %}</td>
|
<td>{{ ticketcc_string }} <a data-toggle='tooltip' href='{% url 'helpdesk:ticket_cc' ticket.id %}' title='{% trans "Click here to add / remove people who should receive an e-mail whenever this ticket is updated." %}'><strong><button type="button" class="btn btn-warning btn-xs"><i class="fa fa-share"></i> {% trans "Manage" %}</button></strong></a>{% if SHOW_SUBSCRIBE %}, <strong><a data-toggle='tooltip' href='?subscribe' title='{% trans "Click here to subscribe yourself to this ticket, if you want to receive an e-mail whenever this ticket is updated." %}'><button type="button" class="btn btn-warning btn-xs"><i class="fa fa-rss"></i> {% trans "Subscribe" %}</button></a></strong>{% endif %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Dependencies" %}</th>
|
<th>{% trans "Dependencies" %}</th>
|
||||||
<td>{% for dep in ticket.ticketdependency.all %}
|
<td>{% for dep in ticket.ticketdependency.all %}
|
||||||
{% if forloop.first %}<p>{% trans "This ticket cannot be resolved until the following ticket(s) are resolved" %}</p><ul>{% endif %}
|
{% if forloop.first %}<p>{% trans "This ticket cannot be resolved until the following ticket(s) are resolved" %}</p><ul>{% endif %}
|
||||||
<li><a href='{{ dep.depends_on.get_absolute_url }}'>{{ dep.depends_on.ticket }} {{ dep.depends_on.title }}</a> ({{ dep.depends_on.get_status_display }}) <a href='{% url 'helpdesk_ticket_dependency_del' ticket.id dep.id %}'><button type="button" class="btn btn-warning btn-xs"><i class="fa fa-trash"></i> {% trans "Remove Dependency" %}</button></a></li>
|
<li><a href='{{ dep.depends_on.get_absolute_url }}'>{{ dep.depends_on.ticket }} {{ dep.depends_on.title }}</a> ({{ dep.depends_on.get_status_display }}) <a href='{% url 'helpdesk:ticket_dependency_del' ticket.id dep.id %}'><button type="button" class="btn btn-warning btn-xs"><i class="fa fa-trash"></i> {% trans "Remove Dependency" %}</button></a></li>
|
||||||
{% if forloop.last %}</ul>{% endif %}
|
{% if forloop.last %}</ul>{% endif %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<p>{% trans "This ticket has no dependencies." %}</p>
|
<p>{% trans "This ticket has no dependencies." %}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<p><a data-toggle='tooltip' href='{% url 'helpdesk_ticket_dependency_add' ticket.id %}' title="{% trans "Click on 'Add Dependency', if you want to make this ticket dependent on another ticket. A ticket may not be closed until all tickets it depends on are closed." %}"><button type="button" class="btn btn-primary btn-xs"><i class="fa fa-child"></i> {% trans "Add Dependency" %}</button></a></p>
|
<p><a data-toggle='tooltip' href='{% url 'helpdesk:ticket_dependency_add' ticket.id %}' title="{% trans "Click on 'Add Dependency', if you want to make this ticket dependent on another ticket. A ticket may not be closed until all tickets it depends on are closed." %}"><button type="button" class="btn btn-primary btn-xs"><i class="fa fa-child"></i> {% trans "Add Dependency" %}</button></a></p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -46,6 +46,107 @@ $(document).ready(function() {
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>{% endcomment %}
|
</ul>{% endcomment %}
|
||||||
|
|
||||||
|
<div class="panel-group filter-options" id="accordion">
|
||||||
|
<div id='tabfilter' class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">
|
||||||
|
<a data-toggle="collapse" data-parent="#accordion" href="#collapseOne">
|
||||||
|
{% trans "Change Query" %}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div id="collapseOne" class="panel-collapse collapse in">
|
||||||
|
<div class="panel-body">
|
||||||
|
<form>
|
||||||
|
<select name='select' id='filterBuilderSelect'>
|
||||||
|
<option value='Sort'>{% trans "Sorting" %}</option>
|
||||||
|
<option value='Owner'>{% trans "Owner" %}</option>
|
||||||
|
<option value='Queue'>{% trans "Queue" %}</option>
|
||||||
|
<option value='Status'>{% trans "Status" %}</option>
|
||||||
|
<option value='Keywords'>{% trans "Keywords" %}</option>
|
||||||
|
<option value='Dates'>{% trans "Date Range" %}</option>
|
||||||
|
</select>
|
||||||
|
<input type='button' id='filterBuilderButton' value='+' />
|
||||||
|
{% csrf_token %}</form>
|
||||||
|
|
||||||
|
<form method='get' action='./'>
|
||||||
|
<div class='thumbnail filterBox{% if query_params.sorting %} filterBoxShow{% endif %}' id='filterBoxSort'>
|
||||||
|
<label for='id_sort'>{% trans "Sorting" %}</label>
|
||||||
|
<select id='id_sort' name='sort'>
|
||||||
|
<option value='created'{% ifequal query_params.sorting "created"%} selected='selected'{% endifequal %}>
|
||||||
|
{% trans "Created" %}
|
||||||
|
</option>
|
||||||
|
<option value='title'{% ifequal query_params.sorting "title"%} selected='selected'{% endifequal %}>
|
||||||
|
{% trans "Title" %}
|
||||||
|
</option>
|
||||||
|
<option value='queue'{% ifequal query_params.sorting "queue"%} selected='selected'{% endifequal %}>
|
||||||
|
{% trans "Queue" %}
|
||||||
|
</option>
|
||||||
|
<option value='status'{% ifequal query_params.sorting "status"%} selected='selected'{% endifequal %}>
|
||||||
|
{% trans "Status" %}
|
||||||
|
</option>
|
||||||
|
<option value='priority'{% ifequal query_params.sorting "priority"%} selected='selected'{% endifequal %}>
|
||||||
|
{% trans "Priority" %}
|
||||||
|
</option>
|
||||||
|
<option value='assigned_to'{% ifequal query_params.sorting "assigned_to"%} selected='selected'{% endifequal %}>
|
||||||
|
{% trans "Owner" %}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<label for='id_sortreverse'>{% trans "Reverse" %}</label>
|
||||||
|
<input type='checkbox' name='sortreverse' id='id_sortreverse'{% if query_params.sortreverse %} checked='checked'{% endif %} />
|
||||||
|
<p class='filterHelp'>{% trans "Ordering applied to tickets" %}</p>
|
||||||
|
<input type='button' class='filterBuilderRemove' value='-' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='thumbnail filterBox{% if query_params.filtering.assigned_to__id__in %} filterBoxShow{% endif %}' id='filterBoxOwner'>
|
||||||
|
<label for='id_owners'>{% trans "Owner(s)" %}</label>
|
||||||
|
<select id='id_owners' name='assigned_to' multiple='selected' size='5'>
|
||||||
|
{% for u in user_choices %}
|
||||||
|
<option value='{{ u.id }}'{% if u.id|in_list:query_params.filtering.assigned_to__id__in %} selected='selected'{% endif %}>
|
||||||
|
{{ u.get_username }}{% ifequal u user %} {% trans "(ME)" %}{% endifequal %}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<p class='filterHelp'>{% trans "Ctrl-Click to select multiple options" %}</p>
|
||||||
|
<input type='button' class='filterBuilderRemove' value='-' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='thumbnail filterBox{% if query_params.filtering.queue__id__in %} filterBoxShow{% endif %}' id='filterBoxQueue'>
|
||||||
|
<label for='id_queues'>{% trans "Queue(s)" %}</label><select id='id_queues' name='queue' multiple='selected' size='5'>{% for q in queue_choices %}<option value='{{ q.id }}'{% if q.id|in_list:query_params.filtering.queue__id__in %} selected='selected'{% endif %}>{{ q.title }}</option>{% endfor %}</select>
|
||||||
|
<p class='filterHelp'>{% trans "Ctrl-click to select multiple options" %}</p>
|
||||||
|
<input type='button' class='filterBuilderRemove' value='-' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='thumbnail filterBox{% if query_params.filtering.status__in %} filterBoxShow{% endif %}' id='filterBoxStatus'>
|
||||||
|
<label for='id_statuses'>{% trans "Status(es)" %}</label><select id='id_statuses' name='status' multiple='selected' size='5'>{% for s in status_choices %}<option value='{{ s.0 }}'{% if s.0|in_list:query_params.filtering.status__in %} selected='selected'{% endif %}>{{ s.1 }}</option>{% endfor %}</select>
|
||||||
|
<p class='filterHelp'>{% trans "Ctrl-click to select multiple options" %}</p>
|
||||||
|
<input type='button' class='filterBuilderRemove' value='-' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='thumbnail filterBox{% if query_params.filtering.created__gte or query_params.filtering.created__lte %} filterBoxShow{% endif %}' id='filterBoxDates'>
|
||||||
|
<label for='id_date_from'>{% trans "Date (From)" %}</label><input type='text' name='date_from' value='{{ query_params.filtering.created__gte }}' id='id_date_from' />
|
||||||
|
<label for='id_date_to'>{% trans "Date (To)" %}</label><input type='text' name='date_to' value='{{ query_params.filtering.created__lte }}' id='id_date_to' />
|
||||||
|
<p class='filterHelp'>{% trans "Use YYYY-MM-DD date format, eg 2011-05-29" %}</p>
|
||||||
|
<input type='button' class='filterBuilderRemove' value='-' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='thumbnail filterBox{% if query %} filterBoxShow{% endif %}' id='filterBoxKeywords'>
|
||||||
|
<label for='id_query'>{% trans "Keywords" %}</label><input type='text' name='q' value='{{ query }}' id='id_query' />
|
||||||
|
<p class='filterHelp'>{% trans "Keywords are case-insensitive, and will be looked for in the title, body and submitter fields." %}</p>
|
||||||
|
<input type='button' class='filterBuilderRemove' value='-' />
|
||||||
|
</div>
|
||||||
|
<hr style='clear: both;' />
|
||||||
|
<input class="btn btn-primary" type='submit' value='{% trans "Apply Filter" %}' />
|
||||||
|
{% if from_saved_query and saved_query.user == user %}
|
||||||
|
<p>{% blocktrans with saved_query.title as query_name %}You are currently viewing saved query <strong>"{{ query_name }}"</strong>.{% endblocktrans %} <a href='{% url 'helpdesk:delete_query' saved_query.id %}'>{% trans "Delete Saved Query" %}</a></p>
|
||||||
|
{% endif %}
|
||||||
|
{% if from_saved_query %}
|
||||||
|
<p>{% blocktrans with saved_query.id as query_id %}<a href='../reports/?saved_query={{ query_id }}'>Run a report</a> on this query to see stats and charts for the data listed below.{% endblocktrans %}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% csrf_token %}</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
@ -145,7 +246,7 @@ $(document).ready(function() {
|
|||||||
<hr style='clear: both;' />
|
<hr style='clear: both;' />
|
||||||
<input class="btn btn-primary" type='submit' value='{% trans "Apply Filter" %}' />
|
<input class="btn btn-primary" type='submit' value='{% trans "Apply Filter" %}' />
|
||||||
{% if from_saved_query and saved_query.user == user %}
|
{% if from_saved_query and saved_query.user == user %}
|
||||||
<p>{% blocktrans with saved_query.title as query_name %}You are currently viewing saved query <strong>"{{ query_name }}"</strong>.{% endblocktrans %} <a href='{% url 'helpdesk_delete_query' saved_query.id %}'>{% trans "Delete Saved Query" %}</a></p>
|
<p>{% blocktrans with saved_query.title as query_name %}You are currently viewing saved query <strong>"{{ query_name }}"</strong>.{% endblocktrans %} <a href='{% url 'helpdesk:delete_query' saved_query.id %}'>{% trans "Delete Saved Query" %}</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if from_saved_query %}
|
{% if from_saved_query %}
|
||||||
<p>{% blocktrans with saved_query.id as query_id %}<a href='../reports/?saved_query={{ query_id }}'>Run a report</a> on this query to see stats and charts for the data listed below.{% endblocktrans %}</p>
|
<p>{% blocktrans with saved_query.id as query_id %}<a href='../reports/?saved_query={{ query_id }}'>Run a report</a> on this query to see stats and charts for the data listed below.{% endblocktrans %}</p>
|
||||||
@ -162,7 +263,7 @@ $(document).ready(function() {
|
|||||||
</div>
|
</div>
|
||||||
<div id="collapseTwo" class="panel-collapse collapse">
|
<div id="collapseTwo" class="panel-collapse collapse">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<form method='post' action='{% url 'helpdesk_savequery' %}'>
|
<form method='post' action='{% url 'helpdesk:savequery' %}'>
|
||||||
<input type='hidden' name='query_encoded' value='{{ urlsafe_query }}' />
|
<input type='hidden' name='query_encoded' value='{{ urlsafe_query }}' />
|
||||||
<dl>
|
<dl>
|
||||||
<dt><label for='id_title'>{% trans "Query Name" %}</label></dt>
|
<dt><label for='id_title'>{% trans "Query Name" %}</label></dt>
|
||||||
@ -189,7 +290,7 @@ $(document).ready(function() {
|
|||||||
</div>
|
</div>
|
||||||
<div id="collapseThree" class="panel-collapse collapse">
|
<div id="collapseThree" class="panel-collapse collapse">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<form method='get' action='{% url 'helpdesk_list' %}'>
|
<form method='get' action='{% url 'helpdesk:list' %}'>
|
||||||
<p><label for='id_query_selector'>{% trans "Query" %}</label> <select name='saved_query' id='id_query_selector'>
|
<p><label for='id_query_selector'>{% trans "Query" %}</label> <select name='saved_query' id='id_query_selector'>
|
||||||
{% for q in user_saved_queries %}
|
{% for q in user_saved_queries %}
|
||||||
<option value='{{ q.id }}'>{{ q.title }}{% if q.shared %} (Shared{% ifnotequal user q.user %} by {{ q.user.get_username }}{% endifnotequal %}){% endif %}</option>
|
<option value='{{ q.id }}'>{{ q.title }}{% if q.shared %} (Shared{% ifnotequal user q.user %} by {{ q.user.get_username }}{% endifnotequal %}){% endif %}</option>
|
||||||
@ -220,7 +321,7 @@ $(document).ready(function() {
|
|||||||
<!-- /.panel-heading -->
|
<!-- /.panel-heading -->
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{{ search_message|safe }}
|
{{ search_message|safe }}
|
||||||
<form method='post' action='{% url 'helpdesk_mass_update' %}' id="ticket_mass_update">
|
<form method='post' action='{% url 'helpdesk:mass_update' %}' id="ticket_mass_update">
|
||||||
<table width="100%" class="table table-striped table-bordered table-hover" id="ticketTable" data-page-length='{{ default_tickets_per_page }}'>
|
<table width="100%" class="table table-striped table-bordered table-hover" id="ticketTable" data-page-length='{{ default_tickets_per_page }}'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -17,8 +17,9 @@ 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 [])
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
register.filter(in_list)
|
register.filter(in_list)
|
||||||
|
@ -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()
|
||||||
|
@ -17,8 +17,8 @@ def saved_queries(user):
|
|||||||
return user_saved_queries
|
return user_saved_queries
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import sys
|
import sys
|
||||||
print >> sys.stderr, "'saved_queries' template tag (django-helpdesk) crashed with following error:"
|
print >> sys.stderr, "'saved_queries' template tag (django-helpdesk) crashed with following error:"
|
||||||
print >> sys.stderr, e
|
print >> sys.stderr, e
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
@ -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,11 +28,9 @@ 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:
|
||||||
ticket = Ticket.objects.get(id=number)
|
ticket = Ticket.objects.get(id=number)
|
||||||
except Ticket.DoesNotExist:
|
except Ticket.DoesNotExist:
|
||||||
@ -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,11 +1,8 @@
|
|||||||
# -*- 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:
|
User = get_user_model()
|
||||||
from django.contrib.auth.models import User
|
|
||||||
else:
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
|
|
||||||
def get_staff_user(username='helpdesk.staff', password='password'):
|
def get_staff_user(username='helpdesk.staff', password='password'):
|
||||||
|
61
helpdesk/tests/test_get_email.py
Normal file
61
helpdesk/tests/test_get_email.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
from helpdesk.models import Queue, Ticket
|
||||||
|
from helpdesk.management.commands.get_email import process_email
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.core import mail
|
||||||
|
from django.core.management import call_command
|
||||||
|
from django.test.client import Client
|
||||||
|
from django.utils import six
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
try: # python 3
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
except ImportError: # python 2
|
||||||
|
from urlparse import urlparse
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Python >= 3.3
|
||||||
|
from unittest import mock
|
||||||
|
except ImportError:
|
||||||
|
# Python < 3.3
|
||||||
|
import mock
|
||||||
|
|
||||||
|
class GetEmailTestCase(TestCase):
|
||||||
|
#fixtures = ['emailtemplate.json'] # may don't need this, not testing templates here
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.queue_public = Queue.objects.create(title='Queue 1', slug='QQ', allow_public_submission=True, allow_email_submission=True, email_box_type='local', email_box_local_dir='/var/lib/mail/helpdesk/')
|
||||||
|
|
||||||
|
# tests correct syntax for command line option
|
||||||
|
def test_get_email_quiet_option(self):
|
||||||
|
with mock.patch('helpdesk.management.commands.get_email.process_email') as mocked_processemail:
|
||||||
|
call_command('get_email', quiet=True)
|
||||||
|
mocked_processemail.assert_called_with(quiet=True)
|
||||||
|
call_command('get_email')
|
||||||
|
mocked_processemail.assert_called_with(quiet=False)
|
||||||
|
|
||||||
|
# tests reading emails from a queue and creating tickets
|
||||||
|
def test_read_email(self):
|
||||||
|
test_email = "To: update.public@example.com\nFrom: comment@example.com\nSubject: Some Comment\n\nThis is the helpdesk comment via email."
|
||||||
|
with mock.patch('helpdesk.management.commands.get_email.listdir') as mocked_listdir, \
|
||||||
|
mock.patch('helpdesk.management.commands.get_email.isfile') as mocked_isfile, \
|
||||||
|
mock.patch('builtins.open' if six.PY3 else '__builtin__.open', mock.mock_open(read_data=test_email)):
|
||||||
|
mocked_isfile.return_value = True
|
||||||
|
mocked_listdir.return_value = ['filename1', 'filename2']
|
||||||
|
|
||||||
|
call_command('get_email')
|
||||||
|
|
||||||
|
mocked_listdir.assert_called_with('/var/lib/mail/helpdesk/')
|
||||||
|
mocked_isfile.assert_any_call('/var/lib/mail/helpdesk/filename1')
|
||||||
|
mocked_isfile.assert_any_call('/var/lib/mail/helpdesk/filename2')
|
||||||
|
|
||||||
|
ticket1 = get_object_or_404(Ticket, pk=1)
|
||||||
|
self.assertEqual(ticket1.ticket_for_url, "QQ-%s" % ticket1.id)
|
||||||
|
self.assertEqual(ticket1.description, "This is the helpdesk comment via email.")
|
||||||
|
|
||||||
|
ticket2 = get_object_or_404(Ticket, pk=2)
|
||||||
|
self.assertEqual(ticket2.ticket_for_url, "QQ-%s" % ticket2.id)
|
||||||
|
self.assertEqual(ticket2.description, "This is the helpdesk comment via email.")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,11 +26,11 @@ class TestKBDisabled(TestCase):
|
|||||||
from django.core.urlresolvers import NoReverseMatch
|
from django.core.urlresolvers import NoReverseMatch
|
||||||
|
|
||||||
self.client.login(username=get_staff_user().get_username(), password='password')
|
self.client.login(username=get_staff_user().get_username(), password='password')
|
||||||
self.assertRaises(NoReverseMatch, reverse, 'helpdesk_kb_index')
|
self.assertRaises(NoReverseMatch, reverse, 'helpdesk:kb_index')
|
||||||
try:
|
try:
|
||||||
response = self.client.get(reverse('helpdesk_dashboard'))
|
response = self.client.get(reverse('helpdesk:dashboard'))
|
||||||
except NoReverseMatch as e:
|
except NoReverseMatch as e:
|
||||||
if 'helpdesk_kb_index' in e.message:
|
if 'helpdesk:kb_index' in e.message:
|
||||||
self.fail("Please verify any unchecked references to helpdesk_kb_index (start with navigation.html)")
|
self.fail("Please verify any unchecked references to helpdesk_kb_index (start with navigation.html)")
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
@ -75,7 +75,7 @@ class PerQueueStaffMembershipTestCase(TestCase):
|
|||||||
# Regular users
|
# Regular users
|
||||||
for identifier in self.IDENTIFIERS:
|
for identifier in self.IDENTIFIERS:
|
||||||
self.client.login(username='User_%d' % identifier, password=str(identifier))
|
self.client.login(username='User_%d' % identifier, password=str(identifier))
|
||||||
response = self.client.get(reverse('helpdesk_dashboard'))
|
response = self.client.get(reverse('helpdesk:dashboard'))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
len(response.context['unassigned_tickets']),
|
len(response.context['unassigned_tickets']),
|
||||||
identifier,
|
identifier,
|
||||||
@ -131,7 +131,12 @@ class PerQueueStaffMembershipTestCase(TestCase):
|
|||||||
|
|
||||||
# Superuser
|
# Superuser
|
||||||
self.client.login(username='superuser', password='superuser')
|
self.client.login(username='superuser', password='superuser')
|
||||||
response = self.client.get(reverse('helpdesk_report_index'))
|
response = self.client.get(reverse('helpdesk:report_index'))
|
||||||
|
self.assertEqual(
|
||||||
|
len(response.context['unassigned_tickets']),
|
||||||
|
3,
|
||||||
|
'Unassigned tickets were limited by queue membership for a superuser'
|
||||||
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
len(response.context['dash_tickets']),
|
len(response.context['dash_tickets']),
|
||||||
2,
|
2,
|
||||||
@ -159,7 +164,7 @@ class PerQueueStaffMembershipTestCase(TestCase):
|
|||||||
# Regular users
|
# Regular users
|
||||||
for identifier in self.IDENTIFIERS:
|
for identifier in self.IDENTIFIERS:
|
||||||
self.client.login(username='User_%d' % identifier, password=str(identifier))
|
self.client.login(username='User_%d' % identifier, password=str(identifier))
|
||||||
response = self.client.get(reverse('helpdesk_list'))
|
response = self.client.get(reverse('helpdesk:list'))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
len(response.context['tickets']),
|
len(response.context['tickets']),
|
||||||
identifier * 2,
|
identifier * 2,
|
||||||
@ -178,7 +183,7 @@ class PerQueueStaffMembershipTestCase(TestCase):
|
|||||||
|
|
||||||
# Superuser
|
# Superuser
|
||||||
self.client.login(username='superuser', password='superuser')
|
self.client.login(username='superuser', password='superuser')
|
||||||
response = self.client.get(reverse('helpdesk_list'))
|
response = self.client.get(reverse('helpdesk:list'))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
len(response.context['tickets']),
|
len(response.context['tickets']),
|
||||||
6,
|
6,
|
||||||
@ -195,7 +200,7 @@ class PerQueueStaffMembershipTestCase(TestCase):
|
|||||||
for identifier in self.IDENTIFIERS:
|
for identifier in self.IDENTIFIERS:
|
||||||
self.client.login(username='User_%d' % identifier, password=str(identifier))
|
self.client.login(username='User_%d' % identifier, password=str(identifier))
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse('helpdesk_run_report', kwargs={'report': 'userqueue'})
|
reverse('helpdesk:run_report', kwargs={'report': 'userqueue'})
|
||||||
)
|
)
|
||||||
# Only two columns of data should be present: ticket counts for
|
# Only two columns of data should be present: ticket counts for
|
||||||
# unassigned and this user only
|
# unassigned and this user only
|
||||||
@ -226,7 +231,7 @@ class PerQueueStaffMembershipTestCase(TestCase):
|
|||||||
# Superuser
|
# Superuser
|
||||||
self.client.login(username='superuser', password='superuser')
|
self.client.login(username='superuser', password='superuser')
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse('helpdesk_run_report', kwargs={'report': 'userqueue'})
|
reverse('helpdesk:run_report', kwargs={'report': 'userqueue'})
|
||||||
)
|
)
|
||||||
# Superuser should see ticket counts for all two queues, which includes
|
# Superuser should see ticket counts for all two queues, which includes
|
||||||
# three columns: unassigned and both user 1 and user 2
|
# three columns: unassigned and both user 1 and user 2
|
||||||
|
@ -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)
|
||||||
|
|
||||||
@ -46,7 +60,7 @@ class PublicActionsTestCase(TestCase):
|
|||||||
self.assertTemplateNotUsed(response, 'helpdesk/public_view_form.html')
|
self.assertTemplateNotUsed(response, 'helpdesk/public_view_form.html')
|
||||||
self.assertEqual(ticket.status, Ticket.CLOSED_STATUS)
|
self.assertEqual(ticket.status, Ticket.CLOSED_STATUS)
|
||||||
self.assertEqual(ticket.resolution, resolution_text)
|
self.assertEqual(ticket.resolution, resolution_text)
|
||||||
self.assertEqual(current_followups+1, ticket.followup_set.all().count())
|
self.assertEqual(current_followups + 1, ticket.followup_set.all().count())
|
||||||
|
|
||||||
ticket.resolution = old_resolution
|
ticket.resolution = old_resolution
|
||||||
ticket.status = old_status
|
ticket.status = old_status
|
||||||
|
@ -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):
|
||||||
@ -12,15 +13,18 @@ class TestSavingSharedQuery(TestCase):
|
|||||||
|
|
||||||
def test_cansavequery(self):
|
def test_cansavequery(self):
|
||||||
"""Can a query be saved"""
|
"""Can a query be saved"""
|
||||||
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,
|
||||||
'queue':self.q,
|
data={
|
||||||
'shared':'on',
|
'title': 'ticket on my queue',
|
||||||
'query_encoded':'KGRwMApWZmlsdGVyaW5nCnAxCihkcDIKVnN0YXR1c19faW4KcDMKKGxwNApJMQphSTIKYUkzCmFzc1Zzb3J0aW5nCnA1ClZjcmVhdGVkCnA2CnMu'})
|
'queue': self.q,
|
||||||
|
'shared': 'on',
|
||||||
|
'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,22 +15,19 @@ 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,
|
||||||
'email':self.ticket.submitter_email})
|
'email': self.ticket.submitter_email})
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_ticket_with_changed_queue(self):
|
def test_ticket_with_changed_queue(self):
|
||||||
@ -38,9 +36,9 @@ class TestKBDisabled(TestCase):
|
|||||||
q2 = Queue(title='Q2', slug='q2')
|
q2 = Queue(title='Q2', slug='q2')
|
||||||
q2.save()
|
q2.save()
|
||||||
# grab the URL / params which would have been emailed out to submitter.
|
# grab the URL / params which would have been emailed out to submitter.
|
||||||
url = reverse('helpdesk_public_view')
|
url = reverse('helpdesk:public_view')
|
||||||
params = {'ticket': self.ticket.ticket_for_url,
|
params = {'ticket': self.ticket.ticket_for_url,
|
||||||
'email':self.ticket.submitter_email}
|
'email': self.ticket.submitter_email}
|
||||||
# Pickup the ticket created in setup() and change its queue
|
# Pickup the ticket created in setup() and change its queue
|
||||||
self.ticket.queue = q2
|
self.ticket.queue = q2
|
||||||
self.ticket.save()
|
self.ticket.save()
|
||||||
|
@ -14,13 +14,23 @@ 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',
|
||||||
'description': 'Some Test Ticket',
|
'description': 'Some Test Ticket',
|
||||||
}
|
}
|
||||||
|
|
||||||
self.client = Client()
|
self.client = Client()
|
||||||
|
|
||||||
@ -31,75 +41,85 @@ 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)
|
||||||
|
|
||||||
response = self.client.get(reverse('helpdesk_home'))
|
response = self.client.get(reverse('helpdesk:home'))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
post_data = {
|
post_data = {
|
||||||
'title': 'Test ticket title',
|
'title': 'Test ticket title',
|
||||||
'queue': self.queue_public.id,
|
'queue': self.queue_public.id,
|
||||||
'submitter_email': 'ticket1.submitter@example.com',
|
'submitter_email': 'ticket1.submitter@example.com',
|
||||||
'body': 'Test ticket body',
|
'body': 'Test ticket body',
|
||||||
'priority': 3,
|
'priority': 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.post(reverse('helpdesk_home'), post_data, follow=True)
|
response = self.client.post(reverse('helpdesk:home'), post_data, follow=True)
|
||||||
last_redirect = response.redirect_chain[-1]
|
last_redirect = response.redirect_chain[-1]
|
||||||
last_redirect_url = last_redirect[0]
|
last_redirect_url = last_redirect[0]
|
||||||
last_redirect_status = last_redirect[1]
|
# last_redirect_status = last_redirect[1]
|
||||||
|
|
||||||
# Ensure we landed on the "View" page.
|
# Ensure we landed on the "View" page.
|
||||||
# Django 1.9 compatible way of testing this
|
# Django 1.9 compatible way of testing this
|
||||||
# https://docs.djangoproject.com/en/1.9/releases/1.9/#http-redirects-no-longer-forced-to-absolute-uris
|
# https://docs.djangoproject.com/en/1.9/releases/1.9/#http-redirects-no-longer-forced-to-absolute-uris
|
||||||
urlparts = urlparse(last_redirect_url)
|
urlparts = urlparse(last_redirect_url)
|
||||||
self.assertEqual(urlparts.path, reverse('helpdesk_public_view'))
|
self.assertEqual(urlparts.path, reverse('helpdesk:public_view'))
|
||||||
|
|
||||||
# Ensure submitter, new-queue + update-queue were all emailed.
|
# Ensure submitter, new-queue + update-queue were all emailed.
|
||||||
self.assertEqual(email_count+3, len(mail.outbox))
|
self.assertEqual(email_count + 3, len(mail.outbox))
|
||||||
|
|
||||||
def test_create_ticket_private(self):
|
def test_create_ticket_private(self):
|
||||||
email_count = len(mail.outbox)
|
email_count = len(mail.outbox)
|
||||||
post_data = {
|
post_data = {
|
||||||
'title': 'Private ticket test',
|
'title': 'Private ticket test',
|
||||||
'queue': self.queue_private.id,
|
'queue': self.queue_private.id,
|
||||||
'submitter_email': 'ticket2.submitter@example.com',
|
'submitter_email': 'ticket2.submitter@example.com',
|
||||||
'body': 'Test ticket body',
|
'body': 'Test ticket body',
|
||||||
'priority': 3,
|
'priority': 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.post(reverse('helpdesk_home'), post_data)
|
response = self.client.post(reverse('helpdesk:home'), post_data)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(email_count, len(mail.outbox))
|
self.assertEqual(email_count, len(mail.outbox))
|
||||||
self.assertContains(response, 'Select a valid choice.')
|
self.assertContains(response, 'Select a valid choice.')
|
||||||
|
|
||||||
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',
|
||||||
'submitter_email': 'ticket3.submitter@example.com',
|
'submitter_email': 'ticket3.submitter@example.com',
|
||||||
'body': 'Test ticket body',
|
'body': 'Test ticket body',
|
||||||
'priority': 3,
|
'priority': 3,
|
||||||
'custom_textfield': 'This is my custom text.',
|
'custom_textfield': 'This is my custom text.',
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.post(reverse('helpdesk_home'), post_data, follow=True)
|
response = self.client.post(reverse('helpdesk:home'), post_data, follow=True)
|
||||||
|
|
||||||
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
|
||||||
# https://docs.djangoproject.com/en/1.9/releases/1.9/#http-redirects-no-longer-forced-to-absolute-uris
|
# https://docs.djangoproject.com/en/1.9/releases/1.9/#http-redirects-no-longer-forced-to-absolute-uris
|
||||||
urlparts = urlparse(last_redirect_url)
|
urlparts = urlparse(last_redirect_url)
|
||||||
self.assertEqual(urlparts.path, reverse('helpdesk_public_view'))
|
self.assertEqual(urlparts.path, reverse('helpdesk:public_view'))
|
||||||
|
|
||||||
# Ensure only two e-mails were sent - submitter & updated.
|
# Ensure only two e-mails were sent - submitter & updated.
|
||||||
self.assertEqual(email_count+2, len(mail.outbox))
|
self.assertEqual(email_count + 2, len(mail.outbox))
|
||||||
|
5
helpdesk/tests/urls.py
Normal file
5
helpdesk/tests/urls.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.conf.urls import include, url
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^helpdesk/', include('helpdesk.urls', namespace='helpdesk')),
|
||||||
|
]
|
109
helpdesk/urls.py
109
helpdesk/urls.py
@ -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, 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:
|
||||||
@ -32,162 +29,160 @@ class DirectTemplateView(TemplateView):
|
|||||||
context[key] = value
|
context[key] = value
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
app_name = 'helpdesk'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^dashboard/$',
|
url(r'^dashboard/$',
|
||||||
staff.dashboard,
|
staff.dashboard,
|
||||||
name='helpdesk_dashboard'),
|
name='dashboard'),
|
||||||
|
|
||||||
url(r'^tickets/$',
|
url(r'^tickets/$',
|
||||||
staff.ticket_list,
|
staff.ticket_list,
|
||||||
name='helpdesk_list'),
|
name='list'),
|
||||||
|
|
||||||
url(r'^tickets/update/$',
|
url(r'^tickets/update/$',
|
||||||
staff.mass_update,
|
staff.mass_update,
|
||||||
name='helpdesk_mass_update'),
|
name='mass_update'),
|
||||||
|
|
||||||
url(r'^tickets/submit/$',
|
url(r'^tickets/submit/$',
|
||||||
staff.create_ticket,
|
staff.create_ticket,
|
||||||
name='helpdesk_submit'),
|
name='submit'),
|
||||||
|
|
||||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/$',
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/$',
|
||||||
staff.view_ticket,
|
staff.view_ticket,
|
||||||
name='helpdesk_view'),
|
name='view'),
|
||||||
|
|
||||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/followup_edit/(?P<followup_id>[0-9]+)/$',
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/followup_edit/(?P<followup_id>[0-9]+)/$',
|
||||||
staff.followup_edit,
|
staff.followup_edit,
|
||||||
name='helpdesk_followup_edit'),
|
name='followup_edit'),
|
||||||
|
|
||||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/followup_delete/(?P<followup_id>[0-9]+)/$',
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/followup_delete/(?P<followup_id>[0-9]+)/$',
|
||||||
staff.followup_delete,
|
staff.followup_delete,
|
||||||
name='helpdesk_followup_delete'),
|
name='followup_delete'),
|
||||||
|
|
||||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/edit/$',
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/edit/$',
|
||||||
staff.edit_ticket,
|
staff.edit_ticket,
|
||||||
name='helpdesk_edit'),
|
name='edit'),
|
||||||
|
|
||||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/update/$',
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/update/$',
|
||||||
staff.update_ticket,
|
staff.update_ticket,
|
||||||
name='helpdesk_update'),
|
name='update'),
|
||||||
|
|
||||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/delete/$',
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/delete/$',
|
||||||
staff.delete_ticket,
|
staff.delete_ticket,
|
||||||
name='helpdesk_delete'),
|
name='delete'),
|
||||||
|
|
||||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/hold/$',
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/hold/$',
|
||||||
staff.hold_ticket,
|
staff.hold_ticket,
|
||||||
name='helpdesk_hold'),
|
name='hold'),
|
||||||
|
|
||||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/unhold/$',
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/unhold/$',
|
||||||
staff.unhold_ticket,
|
staff.unhold_ticket,
|
||||||
name='helpdesk_unhold'),
|
name='unhold'),
|
||||||
|
|
||||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/cc/$',
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/cc/$',
|
||||||
staff.ticket_cc,
|
staff.ticket_cc,
|
||||||
name='helpdesk_ticket_cc'),
|
name='ticket_cc'),
|
||||||
|
|
||||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/cc/add/$',
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/cc/add/$',
|
||||||
staff.ticket_cc_add,
|
staff.ticket_cc_add,
|
||||||
name='helpdesk_ticket_cc_add'),
|
name='ticket_cc_add'),
|
||||||
|
|
||||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/cc/delete/(?P<cc_id>[0-9]+)/$',
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/cc/delete/(?P<cc_id>[0-9]+)/$',
|
||||||
staff.ticket_cc_del,
|
staff.ticket_cc_del,
|
||||||
name='helpdesk_ticket_cc_del'),
|
name='ticket_cc_del'),
|
||||||
|
|
||||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/dependency/add/$',
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/dependency/add/$',
|
||||||
staff.ticket_dependency_add,
|
staff.ticket_dependency_add,
|
||||||
name='helpdesk_ticket_dependency_add'),
|
name='ticket_dependency_add'),
|
||||||
|
|
||||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/dependency/delete/(?P<dependency_id>[0-9]+)/$',
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/dependency/delete/(?P<dependency_id>[0-9]+)/$',
|
||||||
staff.ticket_dependency_del,
|
staff.ticket_dependency_del,
|
||||||
name='helpdesk_ticket_dependency_del'),
|
name='ticket_dependency_del'),
|
||||||
|
|
||||||
url(r'^tickets/(?P<ticket_id>[0-9]+)/attachment_delete/(?P<attachment_id>[0-9]+)/$',
|
url(r'^tickets/(?P<ticket_id>[0-9]+)/attachment_delete/(?P<attachment_id>[0-9]+)/$',
|
||||||
staff.attachment_del,
|
staff.attachment_del,
|
||||||
name='helpdesk_attachment_del'),
|
name='attachment_del'),
|
||||||
|
|
||||||
url(r'^raw/(?P<type>\w+)/$',
|
url(r'^raw/(?P<type>\w+)/$',
|
||||||
staff.raw_details,
|
staff.raw_details,
|
||||||
name='helpdesk_raw'),
|
name='raw'),
|
||||||
|
|
||||||
url(r'^rss/$',
|
url(r'^rss/$',
|
||||||
staff.rss_list,
|
staff.rss_list,
|
||||||
name='helpdesk_rss_index'),
|
name='rss_index'),
|
||||||
|
|
||||||
url(r'^reports/$',
|
url(r'^reports/$',
|
||||||
staff.report_index,
|
staff.report_index,
|
||||||
name='helpdesk_report_index'),
|
name='report_index'),
|
||||||
|
|
||||||
url(r'^reports/(?P<report>\w+)/$',
|
url(r'^reports/(?P<report>\w+)/$',
|
||||||
staff.run_report,
|
staff.run_report,
|
||||||
name='helpdesk_run_report'),
|
name='run_report'),
|
||||||
|
|
||||||
url(r'^save_query/$',
|
url(r'^save_query/$',
|
||||||
staff.save_query,
|
staff.save_query,
|
||||||
name='helpdesk_savequery'),
|
name='savequery'),
|
||||||
|
|
||||||
url(r'^delete_query/(?P<id>[0-9]+)/$',
|
url(r'^delete_query/(?P<id>[0-9]+)/$',
|
||||||
staff.delete_saved_query,
|
staff.delete_saved_query,
|
||||||
name='helpdesk_delete_query'),
|
name='delete_query'),
|
||||||
|
|
||||||
url(r'^settings/$',
|
url(r'^settings/$',
|
||||||
staff.user_settings,
|
staff.user_settings,
|
||||||
name='helpdesk_user_settings'),
|
name='user_settings'),
|
||||||
|
|
||||||
url(r'^ignore/$',
|
url(r'^ignore/$',
|
||||||
staff.email_ignore,
|
staff.email_ignore,
|
||||||
name='helpdesk_email_ignore'),
|
name='email_ignore'),
|
||||||
|
|
||||||
url(r'^ignore/add/$',
|
url(r'^ignore/add/$',
|
||||||
staff.email_ignore_add,
|
staff.email_ignore_add,
|
||||||
name='helpdesk_email_ignore_add'),
|
name='email_ignore_add'),
|
||||||
|
|
||||||
url(r'^ignore/delete/(?P<id>[0-9]+)/$',
|
url(r'^ignore/delete/(?P<id>[0-9]+)/$',
|
||||||
staff.email_ignore_del,
|
staff.email_ignore_del,
|
||||||
name='helpdesk_email_ignore_del'),
|
name='email_ignore_del'),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url(r'^$',
|
url(r'^$',
|
||||||
public.homepage,
|
public.homepage,
|
||||||
name='helpdesk_home'),
|
name='home'),
|
||||||
|
|
||||||
url(r'^view/$',
|
url(r'^view/$',
|
||||||
public.view_ticket,
|
public.view_ticket,
|
||||||
name='helpdesk_public_view'),
|
name='public_view'),
|
||||||
|
|
||||||
url(r'^change_language/$',
|
url(r'^change_language/$',
|
||||||
public.change_language,
|
public.change_language,
|
||||||
name='helpdesk_public_change_language'),
|
name='public_change_language'),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url(r'^rss/user/(?P<user_name>[^/]+)/$',
|
url(r'^rss/user/(?P<user_name>[^/]+)/$',
|
||||||
login_required(feeds.OpenTicketsByUser()),
|
login_required(feeds.OpenTicketsByUser()),
|
||||||
name='helpdesk_rss_user'),
|
name='rss_user'),
|
||||||
|
|
||||||
url(r'^rss/user/(?P<user_name>[^/]+)/(?P<queue_slug>[A-Za-z0-9_-]+)/$',
|
url(r'^rss/user/(?P<user_name>[^/]+)/(?P<queue_slug>[A-Za-z0-9_-]+)/$',
|
||||||
login_required(feeds.OpenTicketsByUser()),
|
login_required(feeds.OpenTicketsByUser()),
|
||||||
name='helpdesk_rss_user_queue'),
|
name='rss_user_queue'),
|
||||||
|
|
||||||
url(r'^rss/queue/(?P<queue_slug>[A-Za-z0-9_-]+)/$',
|
url(r'^rss/queue/(?P<queue_slug>[A-Za-z0-9_-]+)/$',
|
||||||
login_required(feeds.OpenTicketsByQueue()),
|
login_required(feeds.OpenTicketsByQueue()),
|
||||||
name='helpdesk_rss_queue'),
|
name='rss_queue'),
|
||||||
|
|
||||||
url(r'^rss/unassigned/$',
|
url(r'^rss/unassigned/$',
|
||||||
login_required(feeds.UnassignedTickets()),
|
login_required(feeds.UnassignedTickets()),
|
||||||
name='helpdesk_rss_unassigned'),
|
name='rss_unassigned'),
|
||||||
|
|
||||||
url(r'^rss/recent_activity/$',
|
url(r'^rss/recent_activity/$',
|
||||||
login_required(feeds.RecentFollowUps()),
|
login_required(feeds.RecentFollowUps()),
|
||||||
name='helpdesk_rss_activity'),
|
name='rss_activity'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url(r'^api/(?P<method>[a-z_-]+)/$',
|
|
||||||
api.api,
|
|
||||||
name='helpdesk_api'),
|
|
||||||
|
|
||||||
url(r'^login/$',
|
url(r'^login/$',
|
||||||
auth_views.login,
|
auth_views.login,
|
||||||
{'template_name': 'helpdesk/registration/login.html'},
|
{'template_name': 'helpdesk/registration/login.html'},
|
||||||
@ -203,31 +198,27 @@ if helpdesk_settings.HELPDESK_KB_ENABLED:
|
|||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url(r'^kb/$',
|
url(r'^kb/$',
|
||||||
kb.index,
|
kb.index,
|
||||||
name='helpdesk_kb_index'),
|
name='kb_index'),
|
||||||
|
|
||||||
url(r'^kb/(?P<item>[0-9]+)/$',
|
url(r'^kb/(?P<item>[0-9]+)/$',
|
||||||
kb.item,
|
kb.item,
|
||||||
name='helpdesk_kb_item'),
|
name='kb_item'),
|
||||||
|
|
||||||
url(r'^kb/(?P<item>[0-9]+)/vote/$',
|
url(r'^kb/(?P<item>[0-9]+)/vote/$',
|
||||||
kb.vote,
|
kb.vote,
|
||||||
name='helpdesk_kb_vote'),
|
name='kb_vote'),
|
||||||
|
|
||||||
url(r'^kb/(?P<slug>[A-Za-z0-9_-]+)/$',
|
url(r'^kb/(?P<slug>[A-Za-z0-9_-]+)/$',
|
||||||
kb.category,
|
kb.category,
|
||||||
name='helpdesk_kb_category'),
|
name='kb_category'),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url(r'^api/$',
|
|
||||||
TemplateView.as_view(template_name='helpdesk/help_api.html'),
|
|
||||||
name='helpdesk_api_help'),
|
|
||||||
|
|
||||||
url(r'^help/context/$',
|
url(r'^help/context/$',
|
||||||
TemplateView.as_view(template_name='helpdesk/help_context.html'),
|
TemplateView.as_view(template_name='helpdesk/help_context.html'),
|
||||||
name='helpdesk_help_context'),
|
name='help_context'),
|
||||||
|
|
||||||
url(r'^system_settings/$',
|
url(r'^system_settings/$',
|
||||||
DirectTemplateView.as_view(template_name='helpdesk/system_settings.html'),
|
DirectTemplateView.as_view(template_name='helpdesk/system_settings.html'),
|
||||||
name='helpdesk_system_settings'),
|
name='system_settings'),
|
||||||
]
|
]
|
||||||
|
@ -1,341 +0,0 @@
|
|||||||
""" ..
|
|
||||||
django-helpdesk - A Django powered ticket tracker for small enterprise.
|
|
||||||
|
|
||||||
(c) Copyright 2008 Jutda. All Rights Reserved. See LICENSE for details.
|
|
||||||
|
|
||||||
api.py - Wrapper around API calls, and core functions to provide complete
|
|
||||||
API to third party applications.
|
|
||||||
|
|
||||||
The API documentation can be accessed by visiting http://helpdesk/api/help/
|
|
||||||
(obviously, substitute helpdesk for your django-helpdesk URI), or by reading
|
|
||||||
through templates/helpdesk/help_api.html.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django import forms
|
|
||||||
from django.contrib.auth import authenticate
|
|
||||||
try:
|
|
||||||
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.shortcuts import render
|
|
||||||
from django.template import loader, Context
|
|
||||||
import simplejson
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
|
|
||||||
try:
|
|
||||||
from django.utils import timezone
|
|
||||||
except ImportError:
|
|
||||||
from datetime import datetime as timezone
|
|
||||||
|
|
||||||
from helpdesk.forms import TicketForm
|
|
||||||
from helpdesk.lib import send_templated_mail, safe_template_context
|
|
||||||
from helpdesk.models import Ticket, Queue, FollowUp
|
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
STATUS_OK = 200
|
|
||||||
|
|
||||||
STATUS_ERROR = 400
|
|
||||||
STATUS_ERROR_NOT_FOUND = 404
|
|
||||||
STATUS_ERROR_PERMISSIONS = 403
|
|
||||||
STATUS_ERROR_BADMETHOD = 405
|
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
def api(request, method):
|
|
||||||
"""
|
|
||||||
Regardless of any other paramaters, we provide a help screen
|
|
||||||
to the user if they requested one.
|
|
||||||
|
|
||||||
If the user isn't looking for help, then we enforce a few conditions:
|
|
||||||
* The request must be sent via HTTP POST
|
|
||||||
* The request must contain a 'user' and 'password' which
|
|
||||||
must be valid users
|
|
||||||
* The method must match one of the public methods of the API class.
|
|
||||||
|
|
||||||
|
|
||||||
THIS IS DEPRECATED AS OF DECEMBER 2015 AND WILL BE REMOVED IN JANUARY 2016.
|
|
||||||
SEE https://github.com/rossp/django-helpdesk/issues/198 FOR DETAILS
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
warnings.warn("django-helpdesk API will be removed in January 2016. See https://github.com/rossp/django-helpdesk/issues/198 for details.", category=DeprecationWarning)
|
|
||||||
|
|
||||||
if method == 'help':
|
|
||||||
return render(request, template_name='helpdesk/help_api.html')
|
|
||||||
|
|
||||||
if request.method != 'POST':
|
|
||||||
return api_return(STATUS_ERROR_BADMETHOD)
|
|
||||||
|
|
||||||
# TODO: Move away from having the username & password in every request.
|
|
||||||
request.user = authenticate(
|
|
||||||
username=request.POST.get('user', False),
|
|
||||||
password=request.POST.get('password'),
|
|
||||||
)
|
|
||||||
|
|
||||||
if request.user is None:
|
|
||||||
return api_return(STATUS_ERROR_PERMISSIONS)
|
|
||||||
|
|
||||||
api = API(request)
|
|
||||||
if hasattr(api, 'api_public_%s' % method):
|
|
||||||
return getattr(api, 'api_public_%s' % method)()
|
|
||||||
|
|
||||||
return api_return(STATUS_ERROR)
|
|
||||||
|
|
||||||
|
|
||||||
def api_return(status, text='', json=False):
|
|
||||||
content_type = 'text/plain'
|
|
||||||
if status == STATUS_OK and json:
|
|
||||||
content_type = 'text/json'
|
|
||||||
|
|
||||||
if text is None:
|
|
||||||
if status == STATUS_ERROR:
|
|
||||||
text = 'Error'
|
|
||||||
elif status == STATUS_ERROR_NOT_FOUND:
|
|
||||||
text = 'Resource Not Found'
|
|
||||||
elif status == STATUS_ERROR_PERMISSIONS:
|
|
||||||
text = 'Invalid username or password'
|
|
||||||
elif status == STATUS_ERROR_BADMETHOD:
|
|
||||||
text = 'Invalid request method'
|
|
||||||
elif status == STATUS_OK:
|
|
||||||
text = 'OK'
|
|
||||||
|
|
||||||
r = HttpResponse(status=status, content=text, content_type=content_type)
|
|
||||||
|
|
||||||
if status == STATUS_ERROR_BADMETHOD:
|
|
||||||
r.Allow = 'POST'
|
|
||||||
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
class API:
|
|
||||||
def __init__(self, request):
|
|
||||||
self.request = request
|
|
||||||
|
|
||||||
|
|
||||||
def api_public_create_ticket(self):
|
|
||||||
form = TicketForm(self.request.POST)
|
|
||||||
form.fields['queue'].choices = [[q.id, q.title] for q in Queue.objects.all()]
|
|
||||||
form.fields['assigned_to'].choices = [[u.id, u.get_username()] for u in User.objects.filter(is_active=True)]
|
|
||||||
|
|
||||||
if form.is_valid():
|
|
||||||
ticket = form.save(user=self.request.user)
|
|
||||||
return api_return(STATUS_OK, "%s" % ticket.id)
|
|
||||||
else:
|
|
||||||
return api_return(STATUS_ERROR, text=form.errors.as_text())
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
def api_public_find_user(self):
|
|
||||||
username = self.request.POST.get('username', False)
|
|
||||||
|
|
||||||
try:
|
|
||||||
u = User.objects.get(username=username)
|
|
||||||
return api_return(STATUS_OK, "%s" % u.id)
|
|
||||||
|
|
||||||
except User.DoesNotExist:
|
|
||||||
return api_return(STATUS_ERROR, "Invalid username provided")
|
|
||||||
|
|
||||||
|
|
||||||
def api_public_delete_ticket(self):
|
|
||||||
if not self.request.POST.get('confirm', False):
|
|
||||||
return api_return(STATUS_ERROR, "No confirmation provided")
|
|
||||||
|
|
||||||
try:
|
|
||||||
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
|
||||||
except Ticket.DoesNotExist:
|
|
||||||
return api_return(STATUS_ERROR, "Invalid ticket ID")
|
|
||||||
|
|
||||||
ticket.delete()
|
|
||||||
|
|
||||||
return api_return(STATUS_OK)
|
|
||||||
|
|
||||||
|
|
||||||
def api_public_hold_ticket(self):
|
|
||||||
try:
|
|
||||||
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
|
||||||
except Ticket.DoesNotExist:
|
|
||||||
return api_return(STATUS_ERROR, "Invalid ticket ID")
|
|
||||||
|
|
||||||
ticket.on_hold = True
|
|
||||||
ticket.save()
|
|
||||||
|
|
||||||
return api_return(STATUS_OK)
|
|
||||||
|
|
||||||
|
|
||||||
def api_public_unhold_ticket(self):
|
|
||||||
try:
|
|
||||||
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
|
||||||
except Ticket.DoesNotExist:
|
|
||||||
return api_return(STATUS_ERROR, "Invalid ticket ID")
|
|
||||||
|
|
||||||
ticket.on_hold = False
|
|
||||||
ticket.save()
|
|
||||||
|
|
||||||
return api_return(STATUS_OK)
|
|
||||||
|
|
||||||
|
|
||||||
def api_public_add_followup(self):
|
|
||||||
try:
|
|
||||||
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
|
||||||
except Ticket.DoesNotExist:
|
|
||||||
return api_return(STATUS_ERROR, "Invalid ticket ID")
|
|
||||||
|
|
||||||
message = self.request.POST.get('message', None)
|
|
||||||
public = self.request.POST.get('public', 'n')
|
|
||||||
|
|
||||||
if public not in ['y', 'n']:
|
|
||||||
return api_return(STATUS_ERROR, "Invalid 'public' flag")
|
|
||||||
|
|
||||||
if not message:
|
|
||||||
return api_return(STATUS_ERROR, "Blank message")
|
|
||||||
|
|
||||||
f = FollowUp(
|
|
||||||
ticket=ticket,
|
|
||||||
date=timezone.now(),
|
|
||||||
comment=message,
|
|
||||||
user=self.request.user,
|
|
||||||
title='Comment Added',
|
|
||||||
)
|
|
||||||
|
|
||||||
if public:
|
|
||||||
f.public = True
|
|
||||||
|
|
||||||
f.save()
|
|
||||||
|
|
||||||
context = safe_template_context(ticket)
|
|
||||||
context['comment'] = f.comment
|
|
||||||
|
|
||||||
messages_sent_to = []
|
|
||||||
|
|
||||||
if public and ticket.submitter_email:
|
|
||||||
send_templated_mail(
|
|
||||||
'updated_submitter',
|
|
||||||
context,
|
|
||||||
recipients=ticket.submitter_email,
|
|
||||||
sender=ticket.queue.from_address,
|
|
||||||
fail_silently=True,
|
|
||||||
)
|
|
||||||
messages_sent_to.append(ticket.submitter_email)
|
|
||||||
|
|
||||||
if public:
|
|
||||||
for cc in ticket.ticketcc_set.all():
|
|
||||||
if cc.email_address not in messages_sent_to:
|
|
||||||
send_templated_mail(
|
|
||||||
'updated_submitter',
|
|
||||||
context,
|
|
||||||
recipients=cc.email_address,
|
|
||||||
sender=ticket.queue.from_address,
|
|
||||||
fail_silently=True,
|
|
||||||
)
|
|
||||||
messages_sent_to.append(cc.email_address)
|
|
||||||
|
|
||||||
if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to:
|
|
||||||
send_templated_mail(
|
|
||||||
'updated_cc',
|
|
||||||
context,
|
|
||||||
recipients=ticket.queue.updated_ticket_cc,
|
|
||||||
sender=ticket.queue.from_address,
|
|
||||||
fail_silently=True,
|
|
||||||
)
|
|
||||||
messages_sent_to.append(ticket.queue.updated_ticket_cc)
|
|
||||||
|
|
||||||
if (
|
|
||||||
ticket.assigned_to and
|
|
||||||
self.request.user != ticket.assigned_to and
|
|
||||||
ticket.assigned_to.usersettings.settings.get('email_on_ticket_apichange', False) and
|
|
||||||
ticket.assigned_to.email and
|
|
||||||
ticket.assigned_to.email not in messages_sent_to
|
|
||||||
):
|
|
||||||
send_templated_mail(
|
|
||||||
'updated_owner',
|
|
||||||
context,
|
|
||||||
recipients=ticket.assigned_to.email,
|
|
||||||
sender=ticket.queue.from_address,
|
|
||||||
fail_silently=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
ticket.save()
|
|
||||||
|
|
||||||
return api_return(STATUS_OK)
|
|
||||||
|
|
||||||
|
|
||||||
def api_public_resolve(self):
|
|
||||||
try:
|
|
||||||
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
|
|
||||||
except Ticket.DoesNotExist:
|
|
||||||
return api_return(STATUS_ERROR, "Invalid ticket ID")
|
|
||||||
|
|
||||||
resolution = self.request.POST.get('resolution', None)
|
|
||||||
|
|
||||||
if not resolution:
|
|
||||||
return api_return(STATUS_ERROR, "Blank resolution")
|
|
||||||
|
|
||||||
f = FollowUp(
|
|
||||||
ticket=ticket,
|
|
||||||
date=timezone.now(),
|
|
||||||
comment=resolution,
|
|
||||||
user=self.request.user,
|
|
||||||
title='Resolved',
|
|
||||||
public=True,
|
|
||||||
)
|
|
||||||
f.save()
|
|
||||||
|
|
||||||
context = safe_template_context(ticket)
|
|
||||||
context['resolution'] = f.comment
|
|
||||||
|
|
||||||
subject = '%s %s (Resolved)' % (ticket.ticket, ticket.title)
|
|
||||||
|
|
||||||
messages_sent_to = []
|
|
||||||
|
|
||||||
if ticket.submitter_email:
|
|
||||||
send_templated_mail(
|
|
||||||
'resolved_submitter',
|
|
||||||
context,
|
|
||||||
recipients=ticket.submitter_email,
|
|
||||||
sender=ticket.queue.from_address,
|
|
||||||
fail_silently=True,
|
|
||||||
)
|
|
||||||
messages_sent_to.append(ticket.submitter_email)
|
|
||||||
|
|
||||||
for cc in ticket.ticketcc_set.all():
|
|
||||||
if cc.email_address not in messages_sent_to:
|
|
||||||
send_templated_mail(
|
|
||||||
'resolved_submitter',
|
|
||||||
context,
|
|
||||||
recipients=cc.email_address,
|
|
||||||
sender=ticket.queue.from_address,
|
|
||||||
fail_silently=True,
|
|
||||||
)
|
|
||||||
messages_sent_to.append(cc.email_address)
|
|
||||||
|
|
||||||
if ticket.queue.updated_ticket_cc and ticket.queue.updated_ticket_cc not in messages_sent_to:
|
|
||||||
send_templated_mail(
|
|
||||||
'resolved_cc',
|
|
||||||
context,
|
|
||||||
recipients=ticket.queue.updated_ticket_cc,
|
|
||||||
sender=ticket.queue.from_address,
|
|
||||||
fail_silently=True,
|
|
||||||
)
|
|
||||||
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:
|
|
||||||
send_templated_mail(
|
|
||||||
'resolved_resolved',
|
|
||||||
context,
|
|
||||||
recipients=ticket.assigned_to.email,
|
|
||||||
sender=ticket.queue.from_address,
|
|
||||||
fail_silently=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
ticket.resoltuion = f.comment
|
|
||||||
ticket.status = Ticket.RESOLVED_STATUS
|
|
||||||
|
|
||||||
ticket.save()
|
|
||||||
|
|
||||||
return api_return(STATUS_OK)
|
|
@ -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'
|
||||||
@ -39,51 +37,51 @@ class OpenTicketsByUser(Feed):
|
|||||||
return _("Helpdesk: Open Tickets in queue %(queue)s for %(username)s") % {
|
return _("Helpdesk: Open Tickets in queue %(queue)s for %(username)s") % {
|
||||||
'queue': obj['queue'].title,
|
'queue': obj['queue'].title,
|
||||||
'username': obj['user'].get_username(),
|
'username': obj['user'].get_username(),
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
return _("Helpdesk: Open Tickets for %(username)s") % {
|
return _("Helpdesk: Open Tickets for %(username)s") % {
|
||||||
'username': obj['user'].get_username(),
|
'username': obj['user'].get_username(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def description(self, obj):
|
def description(self, obj):
|
||||||
if obj['queue']:
|
if obj['queue']:
|
||||||
return _("Open and Reopened Tickets in queue %(queue)s for %(username)s") % {
|
return _("Open and Reopened Tickets in queue %(queue)s for %(username)s") % {
|
||||||
'queue': obj['queue'].title,
|
'queue': obj['queue'].title,
|
||||||
'username': obj['user'].get_username(),
|
'username': obj['user'].get_username(),
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
return _("Open and Reopened Tickets for %(username)s") % {
|
return _("Open and Reopened Tickets for %(username)s") % {
|
||||||
'username': obj['user'].get_username(),
|
'username': obj['user'].get_username(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def link(self, obj):
|
def link(self, obj):
|
||||||
if obj['queue']:
|
if obj['queue']:
|
||||||
return u'%s?assigned_to=%s&queue=%s' % (
|
return u'%s?assigned_to=%s&queue=%s' % (
|
||||||
reverse('helpdesk_list'),
|
reverse('helpdesk:list'),
|
||||||
obj['user'].id,
|
obj['user'].id,
|
||||||
obj['queue'].id,
|
obj['queue'].id,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return u'%s?assigned_to=%s' % (
|
return u'%s?assigned_to=%s' % (
|
||||||
reverse('helpdesk_list'),
|
reverse('helpdesk:list'),
|
||||||
obj['user'].id,
|
obj['user'].id,
|
||||||
)
|
)
|
||||||
|
|
||||||
def items(self, obj):
|
def items(self, obj):
|
||||||
if obj['queue']:
|
if obj['queue']:
|
||||||
return Ticket.objects.filter(
|
return Ticket.objects.filter(
|
||||||
assigned_to=obj['user']
|
assigned_to=obj['user']
|
||||||
).filter(
|
).filter(
|
||||||
queue=obj['queue']
|
queue=obj['queue']
|
||||||
).filter(
|
).filter(
|
||||||
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
|
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return Ticket.objects.filter(
|
return Ticket.objects.filter(
|
||||||
assigned_to=obj['user']
|
assigned_to=obj['user']
|
||||||
).filter(
|
).filter(
|
||||||
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
|
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
|
||||||
)
|
)
|
||||||
|
|
||||||
def item_pubdate(self, item):
|
def item_pubdate(self, item):
|
||||||
return item.created
|
return item.created
|
||||||
@ -101,19 +99,18 @@ 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(
|
||||||
assigned_to__isnull=True
|
assigned_to__isnull=True
|
||||||
).filter(
|
).filter(
|
||||||
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
|
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
|
||||||
)
|
)
|
||||||
|
|
||||||
def item_pubdate(self, item):
|
def item_pubdate(self, item):
|
||||||
return item.created
|
return item.created
|
||||||
|
|
||||||
|
|
||||||
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()
|
||||||
@ -127,7 +124,7 @@ class RecentFollowUps(Feed):
|
|||||||
|
|
||||||
title = _('Helpdesk: Recent Followups')
|
title = _('Helpdesk: Recent Followups')
|
||||||
description = _('Recent FollowUps, such as e-mail replies, comments, attachments and resolutions')
|
description = _('Recent FollowUps, such as e-mail replies, comments, attachments and resolutions')
|
||||||
link = '/tickets/' # reverse('helpdesk_list')
|
link = '/tickets/' # reverse('helpdesk:list')
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
return FollowUp.objects.order_by('-date')[:20]
|
return FollowUp.objects.order_by('-date')[:20]
|
||||||
@ -143,25 +140,25 @@ class OpenTicketsByQueue(Feed):
|
|||||||
def title(self, obj):
|
def title(self, obj):
|
||||||
return _('Helpdesk: Open Tickets in queue %(queue)s') % {
|
return _('Helpdesk: Open Tickets in queue %(queue)s') % {
|
||||||
'queue': obj.title,
|
'queue': obj.title,
|
||||||
}
|
}
|
||||||
|
|
||||||
def description(self, obj):
|
def description(self, obj):
|
||||||
return _('Open and Reopened Tickets in queue %(queue)s') % {
|
return _('Open and Reopened Tickets in queue %(queue)s') % {
|
||||||
'queue': obj.title,
|
'queue': obj.title,
|
||||||
}
|
}
|
||||||
|
|
||||||
def link(self, obj):
|
def link(self, obj):
|
||||||
return '%s?queue=%s' % (
|
return '%s?queue=%s' % (
|
||||||
reverse('helpdesk_list'),
|
reverse('helpdesk:list'),
|
||||||
obj.id,
|
obj.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
def items(self, obj):
|
def items(self, obj):
|
||||||
return Ticket.objects.filter(
|
return Ticket.objects.filter(
|
||||||
queue=obj
|
queue=obj
|
||||||
).filter(
|
).filter(
|
||||||
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
|
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
|
||||||
)
|
)
|
||||||
|
|
||||||
def item_pubdate(self, item):
|
def item_pubdate(self, item):
|
||||||
return item.created
|
return item.created
|
||||||
@ -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,31 +18,28 @@ 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,
|
})
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
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,
|
})
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
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,
|
})
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def vote(request, item):
|
def vote(request, item):
|
||||||
@ -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,46 +6,48 @@ 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
|
||||||
|
|
||||||
|
|
||||||
def homepage(request):
|
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('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'))
|
||||||
else:
|
else:
|
||||||
return HttpResponseRedirect(reverse('helpdesk_dashboard'))
|
return HttpResponseRedirect(reverse('helpdesk:dashboard'))
|
||||||
except UserSettings.DoesNotExist:
|
except UserSettings.DoesNotExist:
|
||||||
return HttpResponseRedirect(reverse('helpdesk_dashboard'))
|
return HttpResponseRedirect(reverse('helpdesk:dashboard'))
|
||||||
|
|
||||||
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.
|
||||||
return render(request, template_name='helpdesk/public_spam.html')
|
return render(request, template_name='helpdesk/public_spam.html')
|
||||||
else:
|
else:
|
||||||
ticket = form.save()
|
ticket = form.save()
|
||||||
return HttpResponseRedirect('%s?ticket=%s&email=%s'% (
|
return HttpResponseRedirect('%s?ticket=%s&email=%s' % (
|
||||||
reverse('helpdesk_public_view'),
|
reverse('helpdesk:public_view'),
|
||||||
ticket.ticket_for_url,
|
ticket.ticket_for_url,
|
||||||
ticket.submitter_email)
|
ticket.submitter_email)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
queue = Queue.objects.get(slug=request.GET.get('queue', None))
|
queue = Queue.objects.get(slug=request.GET.get('queue', None))
|
||||||
@ -59,36 +61,38 @@ 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
|
})
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
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:
|
||||||
redirect_url += '?close'
|
redirect_url += '?close'
|
||||||
return HttpResponseRedirect(redirect_url)
|
return HttpResponseRedirect(redirect_url)
|
||||||
@ -102,7 +106,7 @@ def view_ticket(request):
|
|||||||
'public': 1,
|
'public': 1,
|
||||||
'title': ticket.title,
|
'title': ticket.title,
|
||||||
'comment': _('Submitter accepted resolution and closed ticket'),
|
'comment': _('Submitter accepted resolution and closed ticket'),
|
||||||
}
|
}
|
||||||
if ticket.assigned_to:
|
if ticket.assigned_to:
|
||||||
request.POST['owner'] = ticket.assigned_to.id
|
request.POST['owner'] = ticket.assigned_to.id
|
||||||
request.GET = {}
|
request.GET = {}
|
||||||
@ -112,27 +116,18 @@ def view_ticket(request):
|
|||||||
# redirect user back to this ticket if possible.
|
# redirect user back to this ticket if possible.
|
||||||
redirect_url = ''
|
redirect_url = ''
|
||||||
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})
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
45
quicktest.py
45
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
|
||||||
@ -61,11 +82,11 @@ class QuickDjangoTest(object):
|
|||||||
"""
|
"""
|
||||||
Fire up the Django test suite from before version 1.2
|
Fire up the Django test suite from before version 1.2
|
||||||
"""
|
"""
|
||||||
settings.configure(DEBUG = True,
|
settings.configure(DEBUG=True,
|
||||||
DATABASE_ENGINE = 'sqlite3',
|
DATABASE_ENGINE='sqlite3',
|
||||||
DATABASE_NAME = os.path.join(self.DIRNAME, 'database.db'),
|
DATABASE_NAME=os.path.join(self.DIRNAME, 'database.db'),
|
||||||
INSTALLED_APPS = self.INSTALLED_APPS + self.apps
|
INSTALLED_APPS=self.INSTALLED_APPS + self.apps
|
||||||
)
|
)
|
||||||
from django.test.simple import run_tests
|
from django.test.simple import run_tests
|
||||||
failures = run_tests(self.apps, verbosity=1)
|
failures = run_tests(self.apps, verbosity=1)
|
||||||
if failures:
|
if failures:
|
||||||
@ -77,8 +98,8 @@ class QuickDjangoTest(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
settings.configure(
|
settings.configure(
|
||||||
DEBUG = True,
|
DEBUG=True,
|
||||||
DATABASES = {
|
DATABASES={
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
'NAME': os.path.join(self.DIRNAME, 'database.db'),
|
'NAME': os.path.join(self.DIRNAME, 'database.db'),
|
||||||
@ -88,10 +109,11 @@ class QuickDjangoTest(object):
|
|||||||
'PORT': '',
|
'PORT': '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
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 = self.apps[0] + '.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)
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
Django>1.6
|
Django>=1.8
|
||||||
django-bootstrap-form>=3.1,<4
|
django-bootstrap-form>=3.1,<4
|
||||||
email-reply-parser
|
email-reply-parser
|
||||||
django-markdown-deux
|
django-markdown-deux
|
||||||
simplejson
|
simplejson
|
||||||
|
six
|
||||||
|
9
setup.py
9
setup.py
@ -6,7 +6,7 @@ from distutils.util import convert_path
|
|||||||
from fnmatch import fnmatchcase
|
from fnmatch import fnmatchcase
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
version = '0.1.18'
|
version = '0.2.0'
|
||||||
|
|
||||||
# Provided as an attribute, so you can append to these instead
|
# Provided as an attribute, so you can append to these instead
|
||||||
# of replicating them:
|
# of replicating them:
|
||||||
@ -137,10 +137,13 @@ setup(
|
|||||||
"Topic :: Office/Business",
|
"Topic :: Office/Business",
|
||||||
"Topic :: Software Development :: Bug Tracking",
|
"Topic :: Software Development :: Bug Tracking",
|
||||||
],
|
],
|
||||||
keywords=['django', 'helpdesk', 'tickets', 'incidents', 'cases'],
|
keywords=['django', 'helpdesk', 'django-helpdesk', 'tickets', 'incidents',
|
||||||
|
'cases', 'bugs', 'track', 'support'],
|
||||||
author='Ross Poulton',
|
author='Ross Poulton',
|
||||||
author_email='ross@rossp.org',
|
author_email='ross@rossp.org',
|
||||||
url='http://github.com/rossp/django-helpdesk',
|
maintainer='Jonathan Barratt',
|
||||||
|
maintainer_email='jonathan@the-im.com',
|
||||||
|
url='https://github.com/django-helpdesk/django-helpdesk',
|
||||||
license='BSD',
|
license='BSD',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
package_data=find_package_data("helpdesk", only_in_packages=False),
|
package_data=find_package_data("helpdesk", only_in_packages=False),
|
||||||
|
Loading…
Reference in New Issue
Block a user