merge changes from updated master

This commit is contained in:
Jonathan Barratt 2016-10-29 13:33:29 +07:00
commit 43c067dd4f
No known key found for this signature in database
GPG Key ID: BCBF01FBE07879DD
75 changed files with 3503 additions and 2791 deletions

25
.coveragerc Normal file
View 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
View File

@ -3,5 +3,6 @@ dist
django_helpdesk.egg-info
docs/html/*
docs/doctrees/*
.coverage
.project
.pydevproject

378
.pylintrc Normal file
View 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

View File

@ -6,18 +6,23 @@ python:
- "3.5"
env:
- DJANGO=1.7.11
- DJANGO=1.8.7
- DJANGO=1.9
matrix:
exclude:
- python: "3.5" # django 1.7 does not support python 3.5
env: DJANGO=1.7.11
- DJANGO=1.8.15
- DJANGO=1.9.10
- DJANGO=1.10.2
install:
- pip install argparse
- pip install coverage
- pip install codecov
- pip install pep8
- pip install -q Django==$DJANGO
- 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
View File

@ -1,13 +1,24 @@
django-helpdesk was originally written by Ross Poulton. 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.
django-helpdesk was originally written by Ross Poulton. After nearly nine
years under his guidance, the project graduated to its own eponymous
organization, and is currently maintained by:
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
Chris Etcp
Daryl Egarr
David Clymer
Loe Spee
Maxim Litnitskiy
Nikolay Panov
Stefano Brentegani
Tony Zhu

View File

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

View File

@ -1,7 +1,6 @@
include README
include UPGRADE
include README.rst
include AUTHORS
include LICENSE*
include CHANGELOG
include requirements.txt
recursive-include helpdesk/static/helpdesk *

View File

@ -1,13 +1,16 @@
django-helpdesk - A Django powered ticket tracker for small businesses.
=======================================================================
.. image:: https://travis-ci.org/rossp/django-helpdesk.png?branch=master
:target: https://travis-ci.org/rossp/django-helpdesk
.. image:: https://travis-ci.org/django-helpdesk/django-helpdesk.png?branch=master
: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.
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
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)
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
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`
@ -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.
https://www.transifex.com/rossp/django-helpdesk/
https://www.transifex.com/django-helpdesk/django-helpdesk/
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.
.. image:: https://secure.travis-ci.org/rossp/django-helpdesk.png?branch=master
:target: https://travis-ci.org/rossp/django-helpdesk

View File

@ -5,7 +5,7 @@ WORKDIR=/tmp/django-helpdesk-build.$$
mkdir $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
/usr/bin/python setup.py sdist upload

View File

@ -1,7 +1,7 @@
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.
*Warning*: The django-helpdesk API is deprecated, and no longer maintained. See https://github.com/django-helpdesk/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.

View File

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

View File

@ -15,7 +15,7 @@ Try using ``pip install django-helpdesk``. Go and have a beer to celebrate Pytho
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``.
@ -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::
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.
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.
Migrate using Django migrations::

View File

@ -1,7 +1,7 @@
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
TEMPLATE_CONTEXT_PROCESSORS = (
@ -9,6 +9,25 @@ First, django-helpdesk needs ``django.core.context_processors.request`` activat
('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.
HELPDESK_DEFAULT_SETTINGS
@ -68,6 +87,10 @@ These changes are visible throughout django-helpdesk
**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
-----------------------------

View File

@ -5,10 +5,14 @@ from helpdesk.models import EscalationExclusion, EmailTemplate, KBItem
from helpdesk.models import TicketChange, Attachment, IgnoreEmail
from helpdesk.models import CustomField
@admin.register(Queue)
class QueueAdmin(admin.ModelAdmin):
list_display = ('title', 'slug', 'email_address', 'locale')
prepopulated_fields = {"slug": ("title",)}
@admin.register(Ticket)
class TicketAdmin(admin.ModelAdmin):
list_display = ('title', 'status', 'assigned_to', 'queue', 'hidden_submitter_email',)
date_hierarchy = 'created'
@ -24,34 +28,38 @@ class TicketAdmin(admin.ModelAdmin):
return ticket.submitter_email
hidden_submitter_email.short_description = _('Submitter E-Mail')
class TicketChangeInline(admin.StackedInline):
model = TicketChange
class AttachmentInline(admin.StackedInline):
model = Attachment
@admin.register(FollowUp)
class FollowUpAdmin(admin.ModelAdmin):
inlines = [TicketChangeInline, AttachmentInline]
@admin.register(KBItem)
class KBItemAdmin(admin.ModelAdmin):
list_display = ('category', 'title', 'last_updated',)
list_display_links = ('title',)
@admin.register(CustomField)
class CustomFieldAdmin(admin.ModelAdmin):
list_display = ('name', 'label', 'data_type')
@admin.register(EmailTemplate)
class EmailTemplateAdmin(admin.ModelAdmin):
list_display = ('template_name', 'heading', '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(EscalationExclusion)
admin.site.register(EmailTemplate, EmailTemplateAdmin)
admin.site.register(KBCategory)
admin.site.register(KBItem, KBItemAdmin)
admin.site.register(IgnoreEmail)
admin.site.register(CustomField, CustomFieldAdmin)

View File

@ -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
value.
The default is : ::
The default is::
Python Interface by Fuzzyman | akismet.py/0.2.0
@ -55,7 +55,7 @@ Usage example::
"""
import os, sys
import os
from urllib import urlencode
import socket
@ -70,7 +70,7 @@ __all__ = (
'Akismet',
'AkismetError',
'APIKeyError',
)
)
__author__ = 'Michael Foord <fuzzyman AT voidspace DOT org DOT uk>'
@ -104,9 +104,13 @@ else:
class AkismetError(Exception):
"""Base class for all akismet exceptions."""
pass
class APIKeyError(AkismetError):
"""Invalid API key."""
pass
class Akismet(object):
"""A class for working with the akismet API"""
@ -120,7 +124,6 @@ class Akismet(object):
self.user_agent = user_agent % (agent, __version__)
self.setAPIKey(key, blog_url)
def _getURL(self):
"""
Fetch the url to make requests to.
@ -129,15 +132,13 @@ class Akismet(object):
"""
return 'http://%s.%s' % (self.key, self.baseurl)
def _safeRequest(self, url, data, headers):
try:
resp = _fetch_url(url, data, headers)
except Exception, e:
except Exception as e:
raise AkismetError(str(e))
return resp
def setAPIKey(self, key=None, blog_url=None):
"""
Set the wordpress API key for all transactions.
@ -151,7 +152,7 @@ class Akismet(object):
"""
if key is None and isfile('apikey.txt'):
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:
self.key = the_file[0]
self.blog_url = the_file[1]
@ -161,7 +162,6 @@ class Akismet(object):
self.key = key
self.blog_url = blog_url
def verify_key(self):
"""
This equates to the ``verify-key`` call against the akismet API.
@ -179,12 +179,12 @@ class Akismet(object):
"""
if self.key is None:
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
url = 'http://%sverify-key' % self.baseurl
# we *don't* trap the error here
# 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)
if resp.lower() == 'valid':
return True
@ -203,13 +203,13 @@ class Akismet(object):
worked out.
"""
data['comment_content'] = comment
if not 'user_ip' in data:
if 'user_ip' not in data:
try:
val = os.environ['REMOTE_ADDR']
except KeyError:
raise AkismetError("No 'user_ip' supplied")
data['user_ip'] = val
if not 'user_agent' in data:
if 'user_agent' not in data:
try:
val = os.environ['HTTP_USER_AGENT']
except KeyError:
@ -226,14 +226,11 @@ class Akismet(object):
data.setdefault('SERVER_ADMIN', os.environ.get('SERVER_ADMIN', ''))
data.setdefault('SERVER_NAME', os.environ.get('SERVER_NAME', ''))
data.setdefault('SERVER_PORT', os.environ.get('SERVER_PORT', ''))
data.setdefault('SERVER_SIGNATURE', os.environ.get('SERVER_SIGNATURE',
''))
data.setdefault('SERVER_SOFTWARE', os.environ.get('SERVER_SOFTWARE',
''))
data.setdefault('SERVER_SIGNATURE', os.environ.get('SERVER_SIGNATURE', ''))
data.setdefault('SERVER_SOFTWARE', os.environ.get('SERVER_SOFTWARE', ''))
data.setdefault('HTTP_ACCEPT', os.environ.get('HTTP_ACCEPT', ''))
data.setdefault('blog', self.blog_url)
def comment_check(self, comment, data=None, build_data=True, DEBUG=False):
"""
This is the function that checks comments.
@ -316,7 +313,7 @@ class Akismet(object):
url = '%scomment-check' % self._getURL()
# we *don't* trap the error here
# 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)
if DEBUG:
return resp
@ -329,7 +326,6 @@ class Akismet(object):
# NOTE: Happens when you get a 'howdy wilbur' response !
raise AkismetError('missing required argument.')
def submit_spam(self, comment, data=None, build_data=True):
"""
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()
# we *don't* trap the error here
# 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)
def submit_ham(self, comment, data=None, build_data=True):
"""
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()
# we *don't* trap the error here
# 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)

View File

@ -1,6 +1,6 @@
from django.apps import AppConfig
class HelpdeskConfig(AppConfig):
name = 'helpdesk'
verbose_name = "Helpdesk"

View File

@ -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
tickets.
"""
from django.core.exceptions import ObjectDoesNotExist
try:
from StringIO import StringIO
except ImportError:
@ -13,27 +15,27 @@ except ImportError:
from django import forms
from django.forms import extras
from django.core.files.storage import default_storage
from django.conf import settings
from django.utils.translation import ugettext as _
try:
from django.contrib.auth import get_user_model
User = get_user_model()
except ImportError:
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth import get_user_model
try:
from django.utils import timezone
except ImportError:
from datetime import datetime as timezone
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
User = get_user_model()
class CustomFieldMixin(object):
"""
Mixin that provides a method to turn CustomFields into an actual field
"""
def customfield_to_field(self, field, instanceargs):
if field.data_type == 'varchar':
fieldclass = forms.CharField
@ -52,7 +54,7 @@ class CustomFieldMixin(object):
fieldclass = forms.ChoiceField
choices = field.choices_as_array
if field.empty_selection_list:
choices.insert(0, ('','---------' ) )
choices.insert(0, ('', '---------'))
instanceargs['choices'] = choices
elif field.data_type == 'boolean':
fieldclass = forms.BooleanField
@ -73,7 +75,9 @@ class CustomFieldMixin(object):
self.fields['custom_%s' % field.name] = fieldclass(**instanceargs)
class EditTicketForm(CustomFieldMixin, forms.ModelForm):
class Meta:
model = Ticket
exclude = ('created', 'modified', 'status', 'on_hold', 'resolution', 'last_escalation', 'assigned_to')
@ -91,15 +95,14 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm):
except TicketCustomFieldValue.DoesNotExist:
initial_value = None
instanceargs = {
'label': field.label,
'help_text': field.help_text,
'required': field.required,
'initial': initial_value,
}
'label': field.label,
'help_text': field.help_text,
'required': field.required,
'initial': initial_value,
}
self.customfield_to_field(field, instanceargs)
def save(self, *args, **kwargs):
for field, value in self.cleaned_data.items():
@ -108,7 +111,7 @@ class EditTicketForm(CustomFieldMixin, forms.ModelForm):
customfield = CustomField.objects.get(name=field_name)
try:
cfv = TicketCustomFieldValue.objects.get(ticket=self.instance, field=customfield)
except:
except ObjectDoesNotExist:
cfv = TicketCustomFieldValue(ticket=self.instance, field=customfield)
cfv.value = value
cfv.save()
@ -117,77 +120,79 @@ class EditTicketForm(CustomFieldMixin, 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:
model = FollowUp
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):
queue = forms.ChoiceField(
label=_('Queue'),
required=True,
choices=()
)
)
title = forms.CharField(
max_length=100,
required=True,
widget=forms.TextInput(attrs={'size':'60'}),
widget=forms.TextInput(attrs={'size': '60'}),
label=_('Summary of the problem'),
)
)
submitter_email = forms.EmailField(
required=False,
label=_('Submitter E-Mail Address'),
widget=forms.TextInput(attrs={'size':'60'}),
widget=forms.TextInput(attrs={'size': '60'}),
help_text=_('This e-mail address will receive copies of all public '
'updates to this ticket.'),
)
'updates to this ticket.'),
)
body = forms.CharField(
widget=forms.Textarea(attrs={'cols': 47, 'rows': 15}),
label=_('Description of Issue'),
required=True,
)
)
assigned_to = forms.ChoiceField(
choices=(),
required=False,
label=_('Case owner'),
help_text=_('If you select an owner other than yourself, they\'ll be '
'e-mailed details of this ticket immediately.'),
)
'e-mailed details of this ticket immediately.'),
)
priority = forms.ChoiceField(
choices=Ticket.PRIORITY_CHOICES,
required=False,
initial='3',
label=_('Priority'),
help_text=_('Please select a priority carefully. If unsure, leave it '
'as \'3\'.'),
)
help_text=_('Please select a priority carefully. If unsure, leave it as \'3\'.'),
)
due_date = forms.DateTimeField(
widget=extras.SelectDateWidget,
required=False,
label=_('Due on'),
)
)
def clean_due_date(self):
data = self.cleaned_data['due_date']
#TODO: add Google calendar update hook
#if not hasattr(self, 'instance') or self.instance.due_date != new_data:
# print "you changed!"
# TODO: add Google calendar update hook
# if not hasattr(self, 'instance') or self.instance.due_date != new_data:
# print "you changed!"
return data
attachment = forms.FileField(
required=False,
label=_('Attach File'),
help_text=_('You can attach a file such as a document or screenshot to this ticket.'),
)
)
def __init__(self, *args, **kwargs):
"""
@ -196,14 +201,13 @@ class TicketForm(CustomFieldMixin, forms.Form):
super(TicketForm, self).__init__(*args, **kwargs)
for field in CustomField.objects.all():
instanceargs = {
'label': field.label,
'help_text': field.help_text,
'required': field.required,
}
'label': field.label,
'help_text': field.help_text,
'required': field.required,
}
self.customfield_to_field(field, instanceargs)
def save(self, user):
"""
Writes and returns a Ticket() object
@ -211,15 +215,15 @@ class TicketForm(CustomFieldMixin, forms.Form):
q = Queue.objects.get(id=int(self.cleaned_data['queue']))
t = Ticket( title = self.cleaned_data['title'],
submitter_email = self.cleaned_data['submitter_email'],
created = timezone.now(),
status = Ticket.OPEN_STATUS,
queue = q,
description = self.cleaned_data['body'],
priority = self.cleaned_data['priority'],
due_date = self.cleaned_data['due_date'],
)
t = Ticket(title=self.cleaned_data['title'],
submitter_email=self.cleaned_data['submitter_email'],
created=timezone.now(),
status=Ticket.OPEN_STATUS,
queue=q,
description=self.cleaned_data['body'],
priority=self.cleaned_data['priority'],
due_date=self.cleaned_data['due_date'],
)
if self.cleaned_data['assigned_to']:
try:
@ -234,16 +238,16 @@ class TicketForm(CustomFieldMixin, forms.Form):
field_name = field.replace('custom_', '', 1)
customfield = CustomField.objects.get(name=field_name)
cfv = TicketCustomFieldValue(ticket=t,
field=customfield,
value=value)
field=customfield,
value=value)
cfv.save()
f = FollowUp( ticket = t,
title = _('Ticket Opened'),
date = timezone.now(),
public = True,
comment = self.cleaned_data['body'],
user = user,
f = FollowUp(ticket=t,
title=_('Ticket Opened'),
date=timezone.now(),
public=True,
comment=self.cleaned_data['body'],
user=user,
)
if self.cleaned_data['assigned_to']:
f.title = _('Ticket Opened & Assigned to %(name)s') % {
@ -262,7 +266,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
filename=filename,
mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream',
size=file.size,
)
)
a.file.save(file.name, file, save=False)
a.save()
@ -287,10 +291,14 @@ class TicketForm(CustomFieldMixin, forms.Form):
sender=q.from_address,
fail_silently=True,
files=files,
)
)
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(
'assigned_owner',
context,
@ -298,7 +306,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
sender=q.from_address,
fail_silently=True,
files=files,
)
)
messages_sent_to.append(t.assigned_to.email)
if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to:
@ -309,10 +317,12 @@ class TicketForm(CustomFieldMixin, forms.Form):
sender=q.from_address,
fail_silently=True,
files=files,
)
)
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(
'newticket_cc',
context,
@ -320,7 +330,7 @@ class TicketForm(CustomFieldMixin, forms.Form):
sender=q.from_address,
fail_silently=True,
files=files,
)
)
return t
@ -330,28 +340,28 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
label=_('Queue'),
required=True,
choices=()
)
)
title = forms.CharField(
max_length=100,
required=True,
widget=forms.TextInput(),
label=_('Summary of your query'),
)
)
submitter_email = forms.EmailField(
required=True,
label=_('Your E-Mail Address'),
help_text=_('We will e-mail you when your ticket is updated.'),
)
)
body = forms.CharField(
widget=forms.Textarea(),
label=_('Description of your issue'),
required=True,
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(
choices=Ticket.PRIORITY_CHOICES,
@ -359,20 +369,20 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
initial='3',
label=_('Urgency'),
help_text=_('Please select a priority carefully.'),
)
)
due_date = forms.DateTimeField(
widget=extras.SelectDateWidget,
required=False,
label=_('Due on'),
)
)
attachment = forms.FileField(
required=False,
label=_('Attach File'),
help_text=_('You can attach a file such as a document or screenshot to this ticket.'),
max_length=1000,
)
)
def __init__(self, *args, **kwargs):
"""
@ -381,10 +391,10 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
super(PublicTicketForm, self).__init__(*args, **kwargs)
for field in CustomField.objects.filter(staff_only=False):
instanceargs = {
'label': field.label,
'help_text': field.help_text,
'required': field.required,
}
'label': field.label,
'help_text': field.help_text,
'required': field.required,
}
self.customfield_to_field(field, instanceargs)
@ -396,15 +406,15 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
q = Queue.objects.get(id=int(self.cleaned_data['queue']))
t = Ticket(
title = self.cleaned_data['title'],
submitter_email = self.cleaned_data['submitter_email'],
created = timezone.now(),
status = Ticket.OPEN_STATUS,
queue = q,
description = self.cleaned_data['body'],
priority = self.cleaned_data['priority'],
due_date = self.cleaned_data['due_date'],
)
title=self.cleaned_data['title'],
submitter_email=self.cleaned_data['submitter_email'],
created=timezone.now(),
status=Ticket.OPEN_STATUS,
queue=q,
description=self.cleaned_data['body'],
priority=self.cleaned_data['priority'],
due_date=self.cleaned_data['due_date'],
)
if q.default_owner and not t.assigned_to:
t.assigned_to = q.default_owner
@ -416,17 +426,17 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
field_name = field.replace('custom_', '', 1)
customfield = CustomField.objects.get(name=field_name)
cfv = TicketCustomFieldValue(ticket=t,
field=customfield,
value=value)
field=customfield,
value=value)
cfv.save()
f = FollowUp(
ticket = t,
title = _('Ticket Opened Via Web'),
date = timezone.now(),
public = True,
comment = self.cleaned_data['body'],
)
ticket=t,
title=_('Ticket Opened Via Web'),
date=timezone.now(),
public=True,
comment=self.cleaned_data['body'],
)
f.save()
@ -440,7 +450,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
filename=filename,
mime_type=mimetypes.guess_type(filename)[0] or 'application/octet-stream',
size=file.size,
)
)
a.file.save(file.name, file, save=False)
a.save()
@ -460,10 +470,13 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
sender=q.from_address,
fail_silently=True,
files=files,
)
)
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(
'assigned_owner',
context,
@ -471,7 +484,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
sender=q.from_address,
fail_silently=True,
files=files,
)
)
messages_sent_to.append(t.assigned_to.email)
if q.new_ticket_cc and q.new_ticket_cc not in messages_sent_to:
@ -482,10 +495,12 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
sender=q.from_address,
fail_silently=True,
files=files,
)
)
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(
'newticket_cc',
context,
@ -493,7 +508,7 @@ class PublicTicketForm(CustomFieldMixin, forms.Form):
sender=q.from_address,
fail_silently=True,
files=files,
)
)
return t
@ -503,25 +518,25 @@ class UserSettingsForm(forms.Form):
label=_('Show Ticket List on Login?'),
help_text=_('Display the ticket list upon login? Otherwise, the dashboard is shown.'),
required=False,
)
)
email_on_ticket_change = forms.BooleanField(
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?'),
required=False,
)
)
email_on_ticket_assign = forms.BooleanField(
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?'),
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.IntegerField(
label=_('Number of tickets to show per page'),
@ -529,20 +544,28 @@ class UserSettingsForm(forms.Form):
required=False,
min_value=1,
max_value=1000,
)
)
use_email_as_submitter = forms.BooleanField(
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.'),
required=False,
)
)
class EmailIgnoreForm(forms.ModelForm):
class Meta:
model = IgnoreEmail
exclude = []
class TicketCCForm(forms.ModelForm):
class Meta:
model = TicketCC
exclude = ('ticket',)
def __init__(self, *args, **kwargs):
super(TicketCCForm, self).__init__(*args, **kwargs)
if helpdesk_settings.HELPDESK_STAFF_ONLY_TICKET_CC:
@ -550,11 +573,10 @@ class TicketCCForm(forms.ModelForm):
else:
users = User.objects.filter(is_active=True).order_by(User.USERNAME_FIELD)
self.fields['user'].queryset = users
class Meta:
model = TicketCC
exclude = ('ticket',)
class TicketDependencyForm(forms.ModelForm):
class Meta:
model = TicketDependency
exclude = ('ticket',)

View File

@ -6,7 +6,7 @@ django-helpdesk - A Django powered ticket tracker for small enterprise.
lib.py - Common functions (eg multipart e-mail)
"""
chart_colours = ('80C65A', '990066', 'FF9900', '3399CC', 'BBCCED', '3399CC', 'FFCC33')
import logging
try:
from base64 import urlsafe_b64encode as b64encode
@ -17,13 +17,20 @@ try:
except ImportError:
from base64 import decodestring as b64decode
import logging
logger = logging.getLogger('helpdesk')
from django.utils.encoding import smart_str
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
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 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
# 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'):
locale = getattr(context['queue'], 'locale', '')
else:
locale = context['queue'].get('locale', 'en')
locale = context['queue'].get('locale', HELPDESK_EMAIL_FALLBACK_LOCALE)
if not locale:
locale = 'en'
locale = HELPDESK_EMAIL_FALLBACK_LOCALE
t = None
try:
@ -82,16 +90,17 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b
try:
t = EmailTemplate.objects.get(template_name__iexact=template_name, locale__isnull=True)
except EmailTemplate.DoesNotExist:
logger.warning('template "%s" does not exist, no mail sent' %
template_name)
return # just ignore if template doesn't exist
logger.warning('template "%s" does not exist, no mail sent',
template_name)
return # just ignore if template doesn't exist
if not sender:
sender = settings.DEFAULT_FROM_EMAIL
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:
from django.template import engines
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(
"%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')
''' keep new lines in html emails '''
from django.utils.safestring import mark_safe
# keep new lines in html emails
if 'comment' in context:
html_txt = context['comment']
html_txt = html_txt.replace('\r\n', '<br>')
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(
"{%% extends '%s' %%}{%% block title %%}%s{%% endblock %%}{%% block content %%}%s{%% endblock %%}" % (email_html_base_file, t.heading, t.html)
).render(context)
"{%% extends '%s' %%}{%% block title %%}"
"%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(
HELPDESK_EMAIL_SUBJECT_TEMPLATE % {
"subject": t.subject,
@ -128,13 +138,11 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b
if recipients.find(','):
recipients = recipients.split(',')
elif type(recipients) != list:
recipients = [recipients,]
recipients = [recipients, ]
msg = EmailMultiAlternatives( subject_part.replace('\n', '').replace('\r', ''),
text_part,
sender,
recipients,
bcc=bcc)
msg = EmailMultiAlternatives(
subject_part.replace('\n', '').replace('\r', ''),
text_part, sender, recipients, bcc=bcc)
msg.attach_alternative(html_part, "text/html")
if files:
@ -225,23 +233,23 @@ def safe_template_context(ticket):
context = {
'queue': {},
'ticket': {},
}
'ticket': {}
}
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)
if callable(attr):
context['queue'][field] = attr()
else:
context['queue'][field] = attr
for field in ( 'title', 'created', 'modified', 'submitter_email',
'status', 'get_status_display', 'on_hold', 'description',
'resolution', 'priority', 'get_priority_display',
'last_escalation', 'ticket', 'ticket_for_url',
'get_status', 'ticket_url', 'staff_url', '_get_assigned_to'
):
for field in ('title', 'created', 'modified', 'submitter_email',
'status', 'get_status_display', 'on_hold', 'description',
'resolution', 'priority', 'get_priority_display',
'last_escalation', 'ticket', 'ticket_for_url',
'get_status', 'ticket_url', 'staff_url', '_get_assigned_to'
):
attr = getattr(ticket, field, None)
if callable(attr):
context['ticket'][field] = '%s' % attr()
@ -277,10 +285,10 @@ def text_is_spam(text, request):
)
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/'
elif hasattr(settings, 'AKISMET_API_KEY'):
ak.setAPIKey(key = settings.AKISMET_API_KEY)
ak.setAPIKey(key=settings.AKISMET_API_KEY)
else:
return False

File diff suppressed because it is too large Load Diff

View File

@ -4,14 +4,15 @@
#
# Translators:
# Translators:
# Morteza Nekoei <cisco.ir@gmail.com>, 2016
msgid ""
msgstr ""
"Project-Id-Version: django-helpdesk\n"
"Report-Msgid-Bugs-To: http://github.com/RossP/django-helpdesk/issues\n"
"POT-Creation-Date: 2014-07-26 14:14+0200\n"
"PO-Revision-Date: 2014-08-01 09:58+0000\n"
"Last-Translator: Ross Poulton <ross@rossp.org>\n"
"Language-Team: Persian (Iran) (http://www.transifex.com/projects/p/django-helpdesk/language/fa_IR/)\n"
"PO-Revision-Date: 2016-10-10 22:42+0000\n"
"Last-Translator: Morteza Nekoei <cisco.ir@gmail.com>\n"
"Language-Team: Persian (Iran) (http://www.transifex.com/rossp/django-helpdesk/language/fa_IR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@ -27,35 +28,35 @@ msgstr ""
#: templates/helpdesk/ticket_list.html:225 views/staff.py:1032
#: views/staff.py:1038 views/staff.py:1044 views/staff.py:1050
msgid "Queue"
msgstr ""
msgstr "صف"
#: forms.py:137
msgid "Summary of the problem"
msgstr ""
msgstr "خلاصه مشکل"
#: forms.py:142
msgid "Submitter E-Mail Address"
msgstr ""
msgstr "آدرس ایمیل ثبت کننده"
#: forms.py:144
msgid ""
"This e-mail address will receive copies of all public updates to this "
"ticket."
msgstr ""
msgstr "این آدرس ایمیل یک نسخه کپی از کلیه‌ی به‌روزرسانی‌های عمومی این تیکت را دریافت خواهد کرد."
#: forms.py:150
msgid "Description of Issue"
msgstr ""
msgstr "توضیح این مسئله"
#: forms.py:157
msgid "Case owner"
msgstr ""
msgstr "مالک"
#: forms.py:158
msgid ""
"If you select an owner other than yourself, they'll be e-mailed details of "
"this ticket immediately."
msgstr ""
msgstr "اگر یک مالک غیر از خودتان را انتخاب نمایید، جزئیات این تیکت به صورت بلادرنگ برای آن‌ها ارسال خواهد شد"
#: forms.py:166 models.py:327 management/commands/escalate_tickets.py:154
#: templates/helpdesk/public_view_ticket.html:23
@ -63,28 +64,28 @@ msgstr ""
#: templates/helpdesk/ticket_desc_table.html:47
#: templates/helpdesk/ticket_list.html:94 views/staff.py:429
msgid "Priority"
msgstr ""
msgstr "اولویت"
#: forms.py:167
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
#: views/staff.py:439
msgid "Due on"
msgstr ""
msgstr "در مدت زمان"
#: forms.py:186 forms.py:370
msgid "Attach File"
msgstr ""
msgstr "ضمیمه کردن فایل"
#: forms.py:187 forms.py:371
msgid "You can attach a file such as a document or screenshot to this ticket."
msgstr ""
msgstr "شما می‌توانید یک فایل مانند مستند یا عکس به این تیکت ضمیمه نمایید."
#: forms.py:240
msgid "Ticket Opened"
msgstr ""
msgstr "تیکت باز است"
#: forms.py:247
#, python-format
@ -93,68 +94,68 @@ msgstr ""
#: forms.py:337
msgid "Summary of your query"
msgstr ""
msgstr "خلاصه پرس و جوی شما"
#: forms.py:342
msgid "Your E-Mail Address"
msgstr ""
msgstr "آدرس ایمیل شما"
#: forms.py:343
msgid "We will e-mail you when your ticket is updated."
msgstr ""
msgstr "زمانی‌ که تیکت به‌روزرسانی شود، برای شما ایمیلی ارسال خواهد شد."
#: forms.py:348
msgid "Description of your issue"
msgstr ""
msgstr "توضیح مشکل شما"
#: forms.py:350
msgid ""
"Please be as descriptive as possible, including any details we may need to "
"address your query."
msgstr ""
msgstr "لطفا تا جای امکان توضیحات لازم را وارد نمایید، توضیحاتی شامل هر نوع جزئیاتی که ما برای یافتن پرس و جوی شما لازم داریم."
#: forms.py:358
msgid "Urgency"
msgstr ""
msgstr "اورژانسی"
#: forms.py:359
msgid "Please select a priority carefully."
msgstr ""
msgstr "لطفا با دقت یک اولویت را انتخاب نمایید."
#: forms.py:419
msgid "Ticket Opened Via Web"
msgstr ""
msgstr "تیکت از طریق وب باز شده است."
#: forms.py:486
msgid "Show Ticket List on Login?"
msgstr ""
msgstr "لیست تیکت‌ها در زمان ورود نمایش داده شود؟"
#: forms.py:487
msgid "Display the ticket list upon login? Otherwise, the dashboard is shown."
msgstr ""
msgstr "لیست تیکت‌ها در زمان ورود نمایش داده شود؟ در غیر اینصورت در داشبورد نمایش داده خواهند شد."
#: forms.py:492
msgid "E-mail me on ticket change?"
msgstr ""
msgstr "تغییرات تیکت به من ایمیل شود؟"
#: forms.py:493
msgid ""
"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?"
msgstr ""
msgstr "اگر شما مالک تیکت هستید و تیکت از طریق وب توسط افراد دیگر تغییر یافت، آیا مایل هستید یک ایمیل اطلاع‌ رسانی دریافت نمایید؟"
#: forms.py:498
msgid "E-mail me when assigned a ticket?"
msgstr ""
msgstr "زمانی‌که یک تیکت تخصیص یافت به من ایمیل ارسال شود؟"
#: forms.py:499
msgid ""
"If you are assigned a ticket via the web, do you want to receive an e-mail?"
msgstr ""
msgstr "اگر شما یک تیکت را از طریق وب تخصیص دادید، آیا مایل هستید یک ایمیل اطلاع‌رسانی دریافت نمایید؟"
#: forms.py:504
msgid "E-mail me when a ticket is changed via the API?"
msgstr ""
msgstr "زمانی‌که یک تیکت از طریق API تغییر یافت، برای من ایمیل ارسال شود؟"
#: forms.py:505
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
msgid "Number of tickets to show per page"
msgstr ""
msgstr "تعداد تیکت‌های قابل نمایش در هر صفحه"
#: forms.py:511
msgid "How many tickets do you want to see on the Ticket List page?"
msgstr ""
msgstr "مایل هستید چه تعداد تیکت در صفحه‌ی لیست تیکت برای شما نمایش داده شود؟"
#: forms.py:518
msgid "Use my e-mail address when submitting tickets?"
msgstr ""
msgstr "ایمیل آدرس من در زمان ثبت تیکت‌ها استفاده شود."
#: forms.py:519
msgid ""
"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."
msgstr ""
msgstr "زمانی که یک تیکت ثبت نمودید، آیا مایل هستید به صورت خودکار آدرس ایمیل خود را به عنوان آدرس ایمیل ثبت کننده استفاده نمایید؟ شما میتوانید در صورت تمایل یک آدرس ایمیل دیگر زمان ثبت تیکت استفاده نمایید."
#: 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
@ -185,7 +186,7 @@ msgstr ""
#: templates/helpdesk/ticket.html:178 templates/helpdesk/ticket_list.html:85
#: templates/helpdesk/ticket_list.html:225 views/staff.py:419
msgid "Title"
msgstr ""
msgstr "عنوان"
#: models.py:40 models.py:822 models.py:1206
msgid "Slug"
@ -201,39 +202,39 @@ msgstr ""
#: templates/helpdesk/email_ignore_list.html:13
#: templates/helpdesk/ticket_cc_list.html:15
msgid "E-Mail Address"
msgstr ""
msgstr "آدرس ایمیل"
#: models.py:49
msgid ""
"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."
msgstr ""
msgstr "کلیه‌ی ایمیل‌های ارسالی برای این صف، از این ایمیل استفاده خواهند کرد. اگر می‌خواهید از IMAP یا POP3 استفاده کنید، آدرس ایمیل شما باید مرتبط با آن ایمیل سرور باشد."
#: models.py:55 models.py:794
msgid "Locale"
msgstr ""
msgstr "موقعیت/محل"
#: models.py:59
msgid ""
"Locale of this queue. All correspondence in this queue will be in this "
"language."
msgstr ""
msgstr "موقعیت این صف. کلیه‌ی پاسخ‌ها و تعاملات در این صف، با این زبان استفاده خواهد شد."
#: models.py:63
msgid "Allow Public Submission?"
msgstr ""
msgstr "اجازه دادن ثبت‌های عمومی؟"
#: models.py:66
msgid "Should this queue be listed on the public submission form?"
msgstr ""
msgstr "آیا این صف در فرم ثبت‌های عمومی لیست شود؟"
#: models.py:71
msgid "Allow E-Mail Submission?"
msgstr ""
msgstr "اجازه‌ دادن ثبت ایمیل‌؟"
#: models.py:74
msgid "Do you want to poll the e-mail box below for new tickets?"
msgstr ""
msgstr "آیا می‌خواهید ایمیل زیر را برای تیکت‌های جدید انتخاب نمایید؟"
#: models.py:79
msgid "Escalation Days"
@ -243,22 +244,22 @@ msgstr ""
msgid ""
"For tickets which are not held, how often do you wish to increase their "
"priority? Set to 0 for no escalation."
msgstr ""
msgstr "برای تیکت‌هایی که نگهداری نشده‌اند، آیا می‌خواهید اولویتی برای آن‌ها در نظر بگیرید؟ عدد ۰ را برای عدم استفاده از اولویت بکار برید."
#: models.py:87
msgid "New Ticket CC Address"
msgstr ""
msgstr "یک آدرس کپی (CC) برای تیکت جدید"
#: models.py:91
msgid ""
"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 "
"e-mail addresses."
msgstr ""
msgstr "اگر یک آدرس ایمیل در این قسمت وارد شود، این آدرس کلیه‌ی تیکت‌های ایجاد شده برای این صف را دریافت خواهد نمود. جهت استفاده از بیش از یک آدرس ایمیل از کاما استفاده نمایید."
#: models.py:97
msgid "Updated Ticket CC Address"
msgstr ""
msgstr "آدرس تیکت (CC) به‌روزرسانی شده"
#: models.py:101
msgid ""

View File

@ -6,18 +6,20 @@
# Translators:
# Alexandre Papin <papin.alexandre@me.com>, 2013
# 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
# kolin22 <kolin22@gmail.com>, 2011
# Ross Poulton <ross@rossp.org>, 2011
# kolin22 <inactive+kolin22@transifex.com>, 2011
# Paul Guichon <fsx999@gmail.com>, 2015
# Ross Poulton <ross@rossp.org>, 2011,2015
msgid ""
msgstr ""
"Project-Id-Version: django-helpdesk\n"
"Report-Msgid-Bugs-To: http://github.com/RossP/django-helpdesk/issues\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"
"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"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@ -497,7 +499,7 @@ msgstr " - En attente"
#: models.py:394
msgid " - Open dependencies"
msgstr ""
msgstr "Dépendance ouverte"
#: models.py:448 models.py:494 models.py:1117 models.py:1280 models.py:1309
#: templates/helpdesk/public_homepage.html:78
@ -548,7 +550,7 @@ msgstr "Suivi"
#: models.py:543
msgid "Follow-ups"
msgstr ""
msgstr "Suivis"
#: models.py:570 models.py:1285
msgid "Field"
@ -578,11 +580,11 @@ msgstr "changé de \"%(old_value)s\" à \"%(new_value)s\""
#: models.py:600
msgid "Ticket change"
msgstr ""
msgstr "Changement de ticket"
#: models.py:601
msgid "Ticket changes"
msgstr ""
msgstr "Changements de ticket"
#: models.py:632
msgid "File"
@ -640,11 +642,11 @@ msgstr "Context disponible: {{ ticket }} - objet du ticket (eg {{ ticket.title }
#: models.py:705
msgid "Pre-set reply"
msgstr ""
msgstr "Réponse préétablie"
#: models.py:706
msgid "Pre-set replies"
msgstr ""
msgstr "Réponse préétablie"
#: models.py:727
msgid ""
@ -658,11 +660,11 @@ msgstr "Jours exclus du processus d'augmentation des priorités"
#: models.py:746
msgid "Escalation exclusion"
msgstr ""
msgstr "Exclusion priorités"
#: models.py:747
msgid "Escalation exclusions"
msgstr ""
msgstr "Exclusion priorités"
#: models.py:760
msgid "Template Name"
@ -713,19 +715,19 @@ msgstr "Langue de ce modèle."
#: models.py:806
msgid "e-mail template"
msgstr ""
msgstr "Modèle d'e-mail"
#: models.py:807
msgid "e-mail templates"
msgstr ""
msgstr "Modèles d'e-mail"
#: models.py:834
msgid "Knowledge base category"
msgstr ""
msgstr "Catégorie de la base de connaissance"
#: models.py:835
msgid "Knowledge base categories"
msgstr ""
msgstr "Catégories de la base de connaissance"
#: models.py:849 templates/helpdesk/kb_index.html:11
#: templates/helpdesk/public_homepage.html:11
@ -770,11 +772,11 @@ msgstr "Non évalué"
#: models.py:901
msgid "Knowledge base item"
msgstr ""
msgstr "Élément de la base de connaissance"
#: models.py:902
msgid "Knowledge base items"
msgstr ""
msgstr "Éléments de la base de connaissance"
#: models.py:926 templates/helpdesk/ticket_list.html:170
msgid "Query Name"
@ -855,11 +857,11 @@ msgstr "Voulez-vous enregistrer les courriels provenant de cette adresse dans la
#: models.py:1101
msgid "Ignored e-mail address"
msgstr ""
msgstr "Adresse e-mail ignorée"
#: models.py:1102
msgid "Ignored e-mail addresses"
msgstr ""
msgstr "Adresses e-mail ignorées"
#: models.py:1124
msgid "User who wishes to receive updates for this ticket."
@ -1024,11 +1026,11 @@ msgstr "Champs personnalisés"
#: models.py:1297
msgid "Ticket custom field value"
msgstr ""
msgstr "Valeur champs personnalisé billet"
#: models.py:1298
msgid "Ticket custom field values"
msgstr ""
msgstr "Valeur champs personnalisé billet"
#: models.py:1315
msgid "Depends On Ticket"
@ -1036,11 +1038,11 @@ msgstr "Dépend du ticket"
#: models.py:1324
msgid "Ticket dependency"
msgstr ""
msgstr "Dépendance du ticket"
#: models.py:1325
msgid "Ticket dependencies"
msgstr ""
msgstr "Dépendances du ticket"
#: management/commands/create_usersettings.py:25
msgid ""
@ -1093,7 +1095,7 @@ msgstr "(Mis à jour)"
#: templates/helpdesk/attribution.html:2
msgid ""
"<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
msgid "Powered by django-helpdesk"
@ -1144,7 +1146,7 @@ msgstr "Supprimer la requête enregistrée"
#: templates/helpdesk/confirm_delete_saved_query.html:6
msgid "Delete Query"
msgstr ""
msgstr "Supprimer la requête"
#: templates/helpdesk/confirm_delete_saved_query.html:8
#, python-format
@ -1152,13 +1154,13 @@ msgid ""
"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-"
"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
msgid ""
"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."
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/delete_ticket.html:10
@ -1183,13 +1185,13 @@ msgstr "Soumettre un Ticket"
#: templates/helpdesk/create_ticket.html:11
#: templates/helpdesk/edit_ticket.html:11
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/edit_ticket.html:11
#: templates/helpdesk/public_homepage.html:28
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/public_homepage.html:55
@ -1205,7 +1207,7 @@ msgid ""
"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"
" 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
msgid "Helpdesk Summary"
@ -1213,37 +1215,37 @@ msgstr "Résumé Helpdesk"
#: templates/helpdesk/dashboard.html:36
msgid "Current Ticket Stats"
msgstr ""
msgstr "Statistiques actuelles des tickets"
#: templates/helpdesk/dashboard.html:37
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
msgid ""
"Average number of days until ticket is closed (tickets opened in last 60 "
"days): "
msgstr ""
msgstr "Délai moyen de fermeture d'un ticket (tickets ouverts dans les 60 derniers jours) :"
#: templates/helpdesk/dashboard.html:39
msgid "Click"
msgstr ""
msgstr "Cliquer"
#: templates/helpdesk/dashboard.html:39
msgid "for detailed average by month."
msgstr ""
msgstr "Pour la moyenne par mois détaillé"
#: templates/helpdesk/dashboard.html:40
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
msgid "Days since opened"
msgstr ""
msgstr "Jours passés depuis l'ouverture"
#: templates/helpdesk/dashboard.html:41
msgid "Number of open tickets"
msgstr ""
msgstr "Nombre de tickets ouverts"
#: templates/helpdesk/dashboard.html:57
msgid "All Tickets submitted by you"
@ -1262,7 +1264,7 @@ msgstr "Dernière mise à jour"
#: templates/helpdesk/dashboard.html:77
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
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
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/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"
" traces of the ticket, including followups, attachments, and updates will be"
" 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
msgid "Edit Ticket"
@ -1313,18 +1315,18 @@ msgstr "Editer le ticket"
#: templates/helpdesk/edit_ticket.html:9
msgid "Edit a Ticket"
msgstr ""
msgstr "Modification du ticket"
#: templates/helpdesk/edit_ticket.html:13
msgid "Note"
msgstr ""
msgstr "Note"
#: templates/helpdesk/edit_ticket.html:13
msgid ""
"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 "
"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
msgid "Save Changes"
@ -1340,14 +1342,14 @@ msgstr "Ignorer l'adresse e-mail"
msgid ""
"To ignore an e-mail address and prevent any emails from that address "
"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 ladresse ci-dessous pour la création automatique de billet"
#: templates/helpdesk/email_ignore_add.html:10
msgid ""
"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,"
" 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
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
msgid "Un-Ignore E-Mail Address"
msgstr ""
msgstr "Ne plus ignorer l'adresse e-mail"
#: templates/helpdesk/email_ignore_del.html:8
#, python-format
@ -1363,7 +1365,7 @@ msgid ""
"Are you sure you wish to stop removing this email address "
"(<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."
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
msgid "Keep Ignoring It"
@ -1526,11 +1528,11 @@ msgstr "Statistiques"
#: templates/helpdesk/navigation.html:24
msgid "Saved Query"
msgstr ""
msgstr "Requête sauvegardée"
#: templates/helpdesk/navigation.html:39
msgid "Change password"
msgstr ""
msgstr "Changer le mot de passe"
#: templates/helpdesk/navigation.html:50
msgid "Search..."
@ -1587,7 +1589,7 @@ msgstr "Impossible d'ouvrir le ticket"
#: templates/helpdesk/public_spam.html:5
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
msgid ""
@ -1595,13 +1597,13 @@ msgid ""
"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 "
"links please try removing them if possible."
msgstr ""
msgstr "Notre système a classé votre soumission comme <strong>spam</strong>, alors nous dans limpossibilité de le sauvegarder. Si ceci nest pas du spam, svp appuyer recul et retaper votre message en sassurant de ne pas être « Spammy », si vous avez beaucoup de Liens, svp les retirés."
#: templates/helpdesk/public_spam.html:7
msgid ""
"We are sorry for any inconvenience, however this check is required to avoid "
"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
msgid "Error:"
@ -1682,7 +1684,7 @@ msgstr "Rapports par File"
#: templates/helpdesk/report_index.html:27 views/staff.py:1049
msgid "Days until ticket closed by Month"
msgstr ""
msgstr "Jours avant fermeture d'un ticket par mois"
#: templates/helpdesk/report_output.html:19
msgid ""
@ -1695,7 +1697,7 @@ msgstr "Selectionnez une requête :"
#: templates/helpdesk/report_output.html:26
msgid "Filter Report"
msgstr ""
msgstr "Filtrer le rapport"
#: templates/helpdesk/report_output.html:29
msgid ""
@ -1739,7 +1741,7 @@ msgid ""
"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."
msgstr ""
msgstr "Ces flux RSS vous permettent davoir un sommaire de vos billets ou tous les billets, pour chacune des files dans votre helpdesk. Par exemple si vous êtes responsable dune file particulière, ceci vous permet de visionner tous les nouveaux billet entrant."
#: templates/helpdesk/rss_list.html:23
msgid "Per-Queue Feeds"
@ -1759,7 +1761,7 @@ msgstr "Modifier les paramètres systèmes"
#: templates/helpdesk/system_settings.html:8
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
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
msgid "Save Ticket CC"
msgstr ""
msgstr "CC sauvegarder billet"
#: templates/helpdesk/ticket_cc_del.html:3
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
msgid "Subscribe"
msgstr ""
msgstr "Souscrire"
#: templates/helpdesk/ticket_desc_table.html:53
msgid ""
"Click here to subscribe yourself to this ticket, if you want to receive an "
"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
msgid "Dependencies"
@ -2063,15 +2065,15 @@ msgstr "Mots-clés"
#: templates/helpdesk/ticket_list.html:72
msgid "Date Range"
msgstr ""
msgstr "Période temporelle"
#: templates/helpdesk/ticket_list.html:100
msgid "Reverse"
msgstr ""
msgstr "Renverser"
#: templates/helpdesk/ticket_list.html:102
msgid "Ordering applied to tickets"
msgstr ""
msgstr "Tri appliqué aux tickets"
#: templates/helpdesk/ticket_list.html:107
msgid "Owner(s)"
@ -2079,11 +2081,11 @@ msgstr "Propriétaire(s)"
#: templates/helpdesk/ticket_list.html:111
msgid "(ME)"
msgstr ""
msgstr "(MOI)"
#: templates/helpdesk/ticket_list.html:115
msgid "Ctrl-Click to select multiple options"
msgstr ""
msgstr "Ctrl-Click pour sélectionner plusieurs options"
#: templates/helpdesk/ticket_list.html:120
msgid "Queue(s)"
@ -2092,7 +2094,7 @@ msgstr "File(s) d'attente"
#: templates/helpdesk/ticket_list.html:121
#: templates/helpdesk/ticket_list.html:127
msgid "Ctrl-click to select multiple options"
msgstr ""
msgstr "Ctrl-click pour sélectionner plusieurs options"
#: templates/helpdesk/ticket_list.html:126
msgid "Status(es)"
@ -2100,21 +2102,21 @@ msgstr "État(s)"
#: templates/helpdesk/ticket_list.html:132
msgid "Date (From)"
msgstr ""
msgstr "Date (du)"
#: templates/helpdesk/ticket_list.html:133
msgid "Date (To)"
msgstr ""
msgstr "Date (au)"
#: templates/helpdesk/ticket_list.html:134
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
msgid ""
"Keywords are case-insensitive, and will be looked for in the title, body and"
" 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
msgid "Apply Filter"
@ -2123,14 +2125,14 @@ msgstr "Appliquer le filtre"
#: templates/helpdesk/ticket_list.html:146
#, python-format
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
#, python-format
msgid ""
"<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."
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:181
@ -2197,7 +2199,7 @@ msgstr "Aucune"
#: templates/helpdesk/ticket_list.html:260
msgid "Inverse"
msgstr ""
msgstr "Inverser"
#: templates/helpdesk/ticket_list.html:262
msgid "With Selected Tickets:"
@ -2235,7 +2237,7 @@ msgstr "Modifier les paramètres de l'utilisateur"
msgid ""
"Use the following options to change the way your helpdesk system works for "
"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
msgid "Save Options"
@ -2260,7 +2262,7 @@ msgstr "Identifiant Helpdesk"
#: templates/helpdesk/registration/login.html:14
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
msgid "Your username and password didn't match. Please try again."

View File

@ -4,14 +4,15 @@
#
# Translators:
# Translators:
# Alan Pevec <apevec@gmail.com>, 2016
msgid ""
msgstr ""
"Project-Id-Version: django-helpdesk\n"
"Report-Msgid-Bugs-To: http://github.com/RossP/django-helpdesk/issues\n"
"POT-Creation-Date: 2014-07-26 14:14+0200\n"
"PO-Revision-Date: 2014-08-01 09:58+0000\n"
"Last-Translator: Ross Poulton <ross@rossp.org>\n"
"Language-Team: Croatian (http://www.transifex.com/projects/p/django-helpdesk/language/hr/)\n"
"PO-Revision-Date: 2016-10-09 16:56+0000\n"
"Last-Translator: Alan Pevec <apevec@gmail.com>\n"
"Language-Team: Croatian (http://www.transifex.com/rossp/django-helpdesk/language/hr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@ -27,29 +28,29 @@ msgstr ""
#: templates/helpdesk/ticket_list.html:225 views/staff.py:1032
#: views/staff.py:1038 views/staff.py:1044 views/staff.py:1050
msgid "Queue"
msgstr ""
msgstr "red"
#: forms.py:137
msgid "Summary of the problem"
msgstr ""
msgstr "sažetak problema"
#: forms.py:142
msgid "Submitter E-Mail Address"
msgstr ""
msgstr "adresa podnositelja"
#: forms.py:144
msgid ""
"This e-mail address will receive copies of all public updates to this "
"ticket."
msgstr ""
msgstr "Ova adresa će primiti kopije svih javnih ažuriranja ovog problema."
#: forms.py:150
msgid "Description of Issue"
msgstr ""
msgstr "opis problema"
#: forms.py:157
msgid "Case owner"
msgstr ""
msgstr "vlasnik slučaja"
#: forms.py:158
msgid ""
@ -63,59 +64,59 @@ msgstr ""
#: templates/helpdesk/ticket_desc_table.html:47
#: templates/helpdesk/ticket_list.html:94 views/staff.py:429
msgid "Priority"
msgstr ""
msgstr "prioritet"
#: forms.py:167
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
#: views/staff.py:439
msgid "Due on"
msgstr ""
msgstr "Rok izvršenja"
#: forms.py:186 forms.py:370
msgid "Attach File"
msgstr ""
msgstr "priloži datoteku"
#: forms.py:187 forms.py:371
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
msgid "Ticket Opened"
msgstr ""
msgstr "otvoreni slučaj"
#: forms.py:247
#, python-format
msgid "Ticket Opened & Assigned to %(name)s"
msgstr ""
msgstr "Slučaj otvoren i dodijeljen %(name)s"
#: forms.py:337
msgid "Summary of your query"
msgstr ""
msgstr "Sažetak Vašeg upita"
#: forms.py:342
msgid "Your E-Mail Address"
msgstr ""
msgstr "adresa Vaše elektroničke pošte"
#: forms.py:343
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
msgid "Description of your issue"
msgstr ""
msgstr "opis Vašeg prolema"
#: forms.py:350
msgid ""
"Please be as descriptive as possible, including any details we may need to "
"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
msgid "Urgency"
msgstr ""
msgstr "hitnost"
#: forms.py:359
msgid "Please select a priority carefully."
@ -123,11 +124,11 @@ msgstr ""
#: forms.py:419
msgid "Ticket Opened Via Web"
msgstr ""
msgstr "Slučaj otvoren na webu"
#: forms.py:486
msgid "Show Ticket List on Login?"
msgstr ""
msgstr "Prikaži listu slučajeva prilikom prijave?"
#: forms.py:487
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_list.html:225 views/staff.py:419
msgid "Title"
msgstr ""
msgstr "naslov"
#: models.py:40 models.py:822 models.py:1206
msgid "Slug"
@ -201,7 +202,7 @@ msgstr ""
#: templates/helpdesk/email_ignore_list.html:13
#: templates/helpdesk/ticket_cc_list.html:15
msgid "E-Mail Address"
msgstr ""
msgstr "adresa elektroničke pošte"
#: models.py:49
msgid ""
@ -229,7 +230,7 @@ msgstr ""
#: models.py:71
msgid "Allow E-Mail Submission?"
msgstr ""
msgstr "Dozvoli prijave elektroničkom poštom?"
#: models.py:74
msgid "Do you want to poll the e-mail box below for new tickets?"
@ -273,11 +274,11 @@ msgstr ""
#: models.py:110
msgid "POP 3"
msgstr ""
msgstr "POP 3"
#: models.py:110
msgid "IMAP"
msgstr ""
msgstr "IMAP"
#: models.py:113
msgid ""
@ -353,30 +354,30 @@ msgstr ""
#: models.py:191 templates/helpdesk/email_ignore_list.html:13
msgid "Queues"
msgstr ""
msgstr "redovi"
#: models.py:245 templates/helpdesk/dashboard.html:15
#: templates/helpdesk/ticket.html:138
msgid "Open"
msgstr ""
msgstr "otvoreno"
#: models.py:246 templates/helpdesk/ticket.html:144
#: templates/helpdesk/ticket.html.py:150 templates/helpdesk/ticket.html:155
#: templates/helpdesk/ticket.html.py:159
msgid "Reopened"
msgstr ""
msgstr "ponovno otvoreno"
#: models.py:247 templates/helpdesk/dashboard.html:15
#: templates/helpdesk/ticket.html:139 templates/helpdesk/ticket.html.py:145
#: templates/helpdesk/ticket.html:151
msgid "Resolved"
msgstr ""
msgstr "riješeno"
#: models.py:248 templates/helpdesk/dashboard.html:15
#: templates/helpdesk/ticket.html:140 templates/helpdesk/ticket.html.py:146
#: templates/helpdesk/ticket.html:152 templates/helpdesk/ticket.html.py:156
msgid "Closed"
msgstr ""
msgstr "zatvoreno"
#: models.py:249 templates/helpdesk/ticket.html:141
#: templates/helpdesk/ticket.html.py:147 templates/helpdesk/ticket.html:160
@ -695,7 +696,7 @@ msgstr ""
#: models.py:788
msgid "HTML"
msgstr ""
msgstr "HTML"
#: models.py:789
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
#: templates/helpdesk/public_homepage.html:11
msgid "Category"
msgstr ""
msgstr "kategorija"
#: models.py:858
msgid "Question"
msgstr ""
msgstr "pitanje"
#: models.py:862
msgid "Answer"
msgstr ""
msgstr "odgovor"
#: models.py:866
msgid "Votes"
@ -752,7 +753,7 @@ msgstr ""
#: models.py:878
msgid "Last Updated"
msgstr ""
msgstr "zadnje ažuriranje"
#: models.py:879
msgid "The date on which this question was most recently changed."
@ -1087,7 +1088,7 @@ msgstr ""
#: templates/helpdesk/attribution.html:2
msgid ""
"<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
msgid "Powered by django-helpdesk"
@ -1110,7 +1111,7 @@ msgstr ""
#: templates/helpdesk/base.html:52 templates/helpdesk/public_base.html:6
#: templates/helpdesk/public_base.html:18
msgid "Helpdesk"
msgstr ""
msgstr "Helpdesk"
#: templates/helpdesk/base.html:62 templates/helpdesk/rss_list.html:9
#: 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:139
msgid "Keywords"
msgstr ""
msgstr "ključne riječi"
#: templates/helpdesk/ticket_list.html:72
msgid "Date Range"

File diff suppressed because it is too large Load Diff

View File

@ -16,12 +16,12 @@ from optparse import make_option
import sys
from django.core.management.base import BaseCommand, CommandError
from django.db.models import Q
from helpdesk.models import EscalationExclusion, Queue
class Command(BaseCommand):
def __init__(self):
BaseCommand.__init__(self)
@ -43,11 +43,12 @@ class Command(BaseCommand):
default=False,
dest='escalate-verbosely',
help='Display a list of dates excluded'),
)
)
def handle(self, *args, **options):
days = options['days']
occurrences = options['occurrences']
# optparse should already handle the `or 1`
occurrences = options['occurrences'] or 1
verbose = False
queue_slugs = options['queues']
queues = []
@ -55,8 +56,6 @@ class Command(BaseCommand):
if options['escalate-verbosely']:
verbose = True
# this should already be handled by optparse
if not occurrences: occurrences = 1
if not (days and occurrences):
raise CommandError('One or more occurrences must be specified.')
@ -116,7 +115,6 @@ def usage():
print(" --verbose, -v: Display a list of dates excluded")
if __name__ == '__main__':
# This script can be run from the command-line or via Django's manage.py.
try:
@ -126,7 +124,7 @@ if __name__ == '__main__':
sys.exit(2)
days = None
occurrences = None
occurrences = 1
verbose = False
queue_slugs = None
queues = []
@ -139,9 +137,8 @@ if __name__ == '__main__':
if o in ('-q', '--queues'):
queue_slugs = a
if o in ('-o', '--occurrences'):
occurrences = int(a)
occurrences = int(a) or 1
if not occurrences: occurrences = 1
if not (days and occurrences):
usage()
sys.exit(2)

View File

@ -25,6 +25,7 @@ from helpdesk.models import Queue
class Command(BaseCommand):
def __init__(self):
BaseCommand.__init__(self)
@ -32,7 +33,7 @@ class Command(BaseCommand):
make_option(
'--queues', '-q',
help='Queues to include (default: all). Use queue slugs'),
)
)
def handle(self, *args, **options):
queue_slugs = options['queues']
@ -71,4 +72,3 @@ class Command(BaseCommand):
)
except IntegrityError:
self.stdout.write(" .. permission already existed, skipping")

View File

@ -10,17 +10,16 @@ users who don't yet have them.
from django.utils.translation import ugettext as _
from django.core.management.base import BaseCommand
try:
from django.contrib.auth import get_user_model
User = get_user_model()
except ImportError:
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from helpdesk.models import UserSettings
from helpdesk.settings import DEFAULT_USER_SETTINGS
User = get_user_model()
class Command(BaseCommand):
"create_usersettings command"
"""create_usersettings command"""
help = _('Check for user without django-helpdesk UserSettings '
'and create settings if required. Uses '
@ -28,10 +27,7 @@ class Command(BaseCommand):
'suit your situation.')
def handle(self, *args, **options):
"handle command line"
"""handle command line"""
for u in User.objects.all():
try:
s = UserSettings.objects.get(user=u)
except UserSettings.DoesNotExist:
s = UserSettings(user=u, settings=DEFAULT_USER_SETTINGS)
s.save()
UserSettings.objects.get_or_create(user=u,
defaults={'settings': DEFAULT_USER_SETTINGS})

View File

@ -28,6 +28,7 @@ from helpdesk.lib import send_templated_mail, safe_template_context
class Command(BaseCommand):
def __init__(self):
BaseCommand.__init__(self)
@ -40,7 +41,7 @@ class Command(BaseCommand):
action='store_true',
default=False,
help='Display a list of dates excluded'),
)
)
def handle(self, *args, **options):
verbose = False
@ -56,7 +57,7 @@ class Command(BaseCommand):
queue_set = queue_slugs.split(',')
for queue in queue_set:
try:
q = Queue.objects.get(slug__exact=queue)
Queue.objects.get(slug__exact=queue)
except Queue.DoesNotExist:
raise CommandError("Queue %s does not exist." % queue)
queues.append(queue)
@ -82,24 +83,23 @@ def escalate_tickets(queues, verbose):
days += 1
workdate = workdate + timedelta(days=1)
req_last_escl_date = date.today() - timedelta(days=days)
if verbose:
print("Processing: %s" % q)
for t in q.ticket_set.filter(
Q(status=Ticket.OPEN_STATUS)
| Q(status=Ticket.REOPENED_STATUS)
).exclude(
priority=1
).filter(
Q(on_hold__isnull=True)
| Q(on_hold=False)
).filter(
Q(last_escalation__lte=req_last_escl_date)
| Q(last_escalation__isnull=True, created__lte=req_last_escl_date)
):
Q(status=Ticket.OPEN_STATUS) |
Q(status=Ticket.REOPENED_STATUS)
).exclude(
priority=1
).filter(
Q(on_hold__isnull=True) |
Q(on_hold=False)
).filter(
Q(last_escalation__lte=req_last_escl_date) |
Q(last_escalation__isnull=True, created__lte=req_last_escl_date)
):
t.last_escalation = timezone.now()
t.priority -= 1
@ -114,7 +114,7 @@ def escalate_tickets(queues, verbose):
recipients=t.submitter_email,
sender=t.queue.from_address,
fail_silently=True,
)
)
if t.queue.updated_ticket_cc:
send_templated_mail(
@ -123,7 +123,7 @@ def escalate_tickets(queues, verbose):
recipients=t.queue.updated_ticket_cc,
sender=t.queue.from_address,
fail_silently=True,
)
)
if t.assigned_to:
send_templated_mail(
@ -132,19 +132,19 @@ def escalate_tickets(queues, verbose):
recipients=t.assigned_to.email,
sender=t.queue.from_address,
fail_silently=True,
)
)
if verbose:
print(" - Esclating %s from %s>%s" % (
t.ticket,
t.priority+1,
t.priority + 1,
t.priority
)
)
)
f = FollowUp(
ticket = t,
title = 'Ticket Escalated',
ticket=t,
title='Ticket Escalated',
date=timezone.now(),
public=True,
comment=_('Ticket escalated after %s days' % q.escalate_days),
@ -152,10 +152,10 @@ def escalate_tickets(queues, verbose):
f.save()
tc = TicketChange(
followup = f,
field = _('Priority'),
old_value = t.priority + 1,
new_value = t.priority,
followup=f,
field=_('Priority'),
old_value=t.priority + 1,
new_value=t.priority,
)
tc.save()

View File

@ -47,7 +47,17 @@ import logging
from time import ctime
STRIPPED_SUBJECT_STRINGS = [
"Re: ",
"Fw: ",
"RE: ",
"FW: ",
"Automatic reply: ",
]
class Command(BaseCommand):
def __init__(self):
BaseCommand.__init__(self)
@ -60,9 +70,10 @@ class Command(BaseCommand):
action='store_true',
dest='quiet',
help='Hide details about each queue/message as they are processed'),
)
)
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.'
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(
@ -103,12 +114,11 @@ def process_email(quiet=False):
logger.addHandler(handler)
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:
q.email_box_interval = 0
queue_time_delta = timedelta(minutes=q.email_box_interval)
if (q.email_box_last_check + queue_time_delta) > timezone.now():
@ -127,15 +137,19 @@ def process_queue(q, logger):
try:
import socks
except ImportError:
logger.error("Queue has been configured with proxy settings, but no socks library was installed. Try to install PySocks via pypi.")
raise ImportError("Queue has been configured with proxy settings, but no socks library was installed. Try to install PySocks via pypi.")
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 = {
'socks4': socks.SOCKS4,
'socks5': socks.SOCKS5,
}.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
else:
if six.PY2:
@ -144,16 +158,21 @@ def process_queue(q, logger):
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 q.email_box_ssl or settings.QUEUE_EMAIL_BOX_SSL:
if not 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))
if not 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:
if not 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))
if not 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")
@ -161,13 +180,11 @@ def process_queue(q, logger):
server.user(q.email_box_user or settings.QUEUE_EMAIL_BOX_USER)
server.pass_(q.email_box_pass or settings.QUEUE_EMAIL_BOX_PASSWORD)
messagesInfo = server.list()[1]
logger.info("Received %s messages from POP3 server" % str(len(messagesInfo)))
for msg in messagesInfo:
msgNum = msg.split(" ")[0]
msgSize = msg.split(" ")[1]
logger.info("Processing message %s" % str(msgNum))
full_message = "\n".join(server.retr(msgNum)[1])
@ -183,15 +200,24 @@ def process_queue(q, logger):
elif email_box_type == 'imap':
if q.email_box_ssl or settings.QUEUE_EMAIL_BOX_SSL:
if not 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))
if not 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:
if not 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))
if not 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))
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.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)
status, data = server.search(None, 'NOT', 'DELETED')
@ -235,20 +261,21 @@ def decodeUnknown(charset, string):
if six.PY2:
if not charset:
try:
return string.decode('utf-8','ignore')
return string.decode('utf-8', 'ignore')
except:
return string.decode('iso8859-1','ignore')
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')
return str(string, encoding='utf-8', errors='replace')
except:
return str(string,encoding='iso8859-1',errors='replace')
return str(string,encoding=charset)
return str(string, encoding='iso8859-1', errors='replace')
return str(string, encoding=charset)
return string
def decode_mail_headers(string):
decoded = decode_header(string)
if six.PY2:
@ -256,13 +283,16 @@ def decode_mail_headers(string):
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, logger):
# 'message' must be an RFC822 formatted message.
msg = message
message = email.message_from_string(msg)
subject = message.get('subject', _('Created from e-mail'))
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 = decode_mail_headers(decodeUnknown(message.get_charset(), sender))
@ -279,7 +309,7 @@ def ticket_from_message(message, queue, logger):
return False
return True
matchobj = re.match(r".*\["+queue.slug+"-(?P<id>\d+)\]", subject)
matchobj = re.match(r".*\[" + queue.slug + "-(?P<id>\d+)\]", subject)
if matchobj:
# This is a reply or forward.
ticket = matchobj.group('id')
@ -299,9 +329,10 @@ def ticket_from_message(message, queue, logger):
if 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':
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:
body_html = part.get_payload(decode=True)
@ -315,7 +346,7 @@ def ticket_from_message(message, queue, logger):
'filename': name,
'content': part.get_payload(decode=True),
'type': part.get_content_type()},
)
)
logger.debug("Found MIME attachment %s" % name)
counter += 1
@ -353,7 +384,7 @@ def ticket_from_message(message, queue, logger):
if smtp_priority in high_priority_types or smtp_importance in high_priority_types:
priority = 2
if ticket == None:
if ticket is None:
t = Ticket(
title=subject,
queue=queue,
@ -364,7 +395,6 @@ def ticket_from_message(message, queue, logger):
)
t.save()
new = True
update = ''
logger.debug("Created new ticket %s-%s" % (t.queue.slug, t.id))
elif t.status == Ticket.CLOSED_STATUS:
@ -372,11 +402,11 @@ def ticket_from_message(message, queue, logger):
t.save()
f = FollowUp(
ticket = t,
title = _('E-Mail Received from %(sender_email)s' % {'sender_email': sender_email}),
date = timezone.now(),
public = True,
comment = body,
ticket=t,
title=_('E-Mail Received from %(sender_email)s' % {'sender_email': sender_email}),
date=timezone.now(),
public=True,
comment=body,
)
if t.status == Ticket.REOPENED_STATUS:
@ -404,12 +434,11 @@ def ticket_from_message(message, queue, logger):
filename=filename,
mime_type=file['type'],
size=len(file['content']),
)
)
a.file.save(filename, ContentFile(file['content']), save=False)
a.save()
logger.info("Attachment '%s' successfully added to ticket." % filename)
context = safe_template_context(t)
if new:
@ -421,7 +450,7 @@ def ticket_from_message(message, queue, logger):
recipients=sender_email,
sender=queue.from_address,
fail_silently=True,
)
)
if queue.new_ticket_cc:
send_templated_mail(
@ -430,7 +459,7 @@ def ticket_from_message(message, queue, logger):
recipients=queue.new_ticket_cc,
sender=queue.from_address,
fail_silently=True,
)
)
if queue.updated_ticket_cc and queue.updated_ticket_cc != queue.new_ticket_cc:
send_templated_mail(
@ -439,15 +468,15 @@ def ticket_from_message(message, queue, logger):
recipients=queue.updated_ticket_cc,
sender=queue.from_address,
fail_silently=True,
)
)
else:
context.update(comment=f.comment)
if t.status == Ticket.REOPENED_STATUS:
update = _(' (Reopened)')
else:
update = _(' (Updated)')
# if t.status == Ticket.REOPENED_STATUS:
# update = _(' (Reopened)')
# else:
# update = _(' (Updated)')
if t.assigned_to:
send_templated_mail(
@ -456,7 +485,7 @@ def ticket_from_message(message, queue, logger):
recipients=t.assigned_to.email,
sender=queue.from_address,
fail_silently=True,
)
)
if queue.updated_ticket_cc:
send_templated_mail(
@ -465,11 +494,10 @@ def ticket_from_message(message, queue, logger):
recipients=queue.updated_ticket_cc,
sender=queue.from_address,
fail_silently=True,
)
)
return t
if __name__ == '__main__':
process_email()

View File

@ -25,7 +25,7 @@ def load_fixture(apps, schema_editor):
def unload_fixture(apps, schema_editor):
"Delete all EmailTemplate objects"
"""Delete all EmailTemplate objects"""
objects = deserialize_fixture()

File diff suppressed because it is too large Load Diff

View File

@ -11,22 +11,27 @@ try:
except:
DEFAULT_USER_SETTINGS = None
if type(DEFAULT_USER_SETTINGS) != type(dict()):
if not isinstance(DEFAULT_USER_SETTINGS, dict):
DEFAULT_USER_SETTINGS = {
'use_email_as_submitter': True,
'email_on_ticket_assign': True,
'email_on_ticket_change': True,
'login_view_ticketlist': True,
'email_on_ticket_apichange': True,
'tickets_per_page': 25
}
'use_email_as_submitter': True,
'email_on_ticket_assign': True,
'email_on_ticket_change': True,
'login_view_ticketlist': True,
'email_on_ticket_apichange': True,
'tickets_per_page': 25
}
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 "/"?
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?
HELPDESK_KB_ENABLED = getattr(settings, 'HELPDESK_KB_ENABLED', True)
@ -34,14 +39,20 @@ HELPDESK_KB_ENABLED = getattr(settings, 'HELPDESK_KB_ENABLED', True)
# show extended navigation by default, to all users, irrespective of staff status?
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)
# 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.
HELPDESK_TRANSLATE_TICKET_COMMENTS_LANG = getattr(settings, 'HELPDESK_TRANSLATE_TICKET_COMMENTS_LANG', ["en", "de", "fr", "it", "ru"])
# list of languages to offer. if set to false,
# 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?
HELPDESK_SHOW_CHANGE_PASSWORD = getattr(settings, 'HELPDESK_SHOW_CHANGE_PASSWORD', False)
@ -50,10 +61,15 @@ HELPDESK_SHOW_CHANGE_PASSWORD = getattr(settings, 'HELPDESK_SHOW_CHANGE_PASSWORD
HELPDESK_FOLLOWUP_MOD = getattr(settings, 'HELPDESK_FOLLOWUP_MOD', False)
# 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?
HELPDESK_VIEW_A_TICKET_PUBLIC = getattr(settings, 'HELPDESK_VIEW_A_TICKET_PUBLIC', True)
@ -61,17 +77,25 @@ HELPDESK_VIEW_A_TICKET_PUBLIC = getattr(settings, 'HELPDESK_VIEW_A_TICKET_PUBLIC
HELPDESK_SUBMIT_A_TICKET_PUBLIC = getattr(settings, 'HELPDESK_SUBMIT_A_TICKET_PUBLIC', True)
###################################
# options for update_ticket views #
###################################
''' options for update_ticket views '''
# allow non-staff users to interact with tickets? this will also change how 'staff_member_required'
# allow non-staff users to interact with tickets?
# this will also change how 'staff_member_required'
# 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.
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'
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
HELPDESK_UPDATE_PUBLIC_DEFAULT = getattr(settings, 'HELPDESK_UPDATE_PUBLIC_DEFAULT', False)
@ -82,18 +106,28 @@ HELPDESK_STAFF_ONLY_TICKET_OWNERS = getattr(settings, 'HELPDESK_STAFF_ONLY_TICKE
# only show staff users in ticket cc drop-down
HELPDESK_STAFF_ONLY_TICKET_CC = getattr(settings, 'HELPDESK_STAFF_ONLY_TICKET_CC', False)
# 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?
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
QUEUE_EMAIL_BOX_TYPE = getattr(settings, 'QUEUE_EMAIL_BOX_TYPE', None)
QUEUE_EMAIL_BOX_SSL = getattr(settings, 'QUEUE_EMAIL_BOX_SSL', None)
@ -101,6 +135,6 @@ QUEUE_EMAIL_BOX_HOST = getattr(settings, 'QUEUE_EMAIL_BOX_HOST', None)
QUEUE_EMAIL_BOX_USER = getattr(settings, 'QUEUE_EMAIL_BOX_USER', None)
QUEUE_EMAIL_BOX_PASSWORD = getattr(settings, 'QUEUE_EMAIL_BOX_PASSWORD', None)
# 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)

View File

@ -1,2 +1,2 @@
{% 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>." %}

View File

@ -23,9 +23,9 @@
<script src="{% static 'helpdesk/bootstrap/bootstrap-3.3.7.min.js' %}" type='text/javascript'></script>
<link href="{% static 'helpdesk/bootstrap/bootstrap-3.3.7.min.css' %}" rel="stylesheet">
{% endif %}
<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_unassigned' %}' type='application/rss+xml' title='{% trans "Unassigned 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_unassigned' %}' type='application/rss+xml' title='{% trans "Unassigned Tickets" %}' />
<style type="text/css">
/* hide google translate top bar */
@ -65,9 +65,9 @@
<div id='footer' class="row">
<div class="col-md-2">{% include "helpdesk/attribution.html" %}</div>
<div class="col-md-2"><a href='{% url 'helpdesk_rss_index' %}'><img src='{% static "helpdesk/rss_icon.png" %}' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "RSS Feeds" %}' border='0' />{% trans "RSS Feeds" %}</a></div>
<div class="col-md-2"><s><a href='{% url 'helpdesk_api_help' %}'>{% trans "API" %}</a></s></div>
{% if user.is_superuser %}<div class="col-md-2"><a href='{% url 'helpdesk_system_settings' %}'>{% trans "System Settings" %}</a></div>{% endif %}
<div class="col-md-2"><a href='{% url 'helpdesk:rss_index' %}'><img src='{% static "helpdesk/rss_icon.png" %}' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "RSS Feeds" %}' border='0' />{% trans "RSS Feeds" %}</a></div>
<div class="col-md-2"><s><a href='{% url 'helpdesk:api_help' %}'>{% trans "API" %}</a></s></div>
{% if user.is_superuser %}<div class="col-md-2"><a href='{% url 'helpdesk:system_settings' %}'>{% trans "System Settings" %}</a></div>{% endif %}
</div>
</div>
{% include "helpdesk/debug.html" %}

View File

@ -20,7 +20,7 @@
<td>{{ ignore.date }}</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><a href='{% url 'helpdesk_email_ignore_del' ignore.id %}'>{% trans "Delete" %}</a></td>
<td><a href='{% url 'helpdesk:email_ignore_del' ignore.id %}'>{% trans "Delete" %}</a></td>
</tr>
{% endfor %}
</tbody>

View File

@ -25,7 +25,7 @@
</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>This API has been deprecated and will be removed in January 2016. Please <a href='https://github.com/django-helpdesk/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>
@ -50,14 +50,14 @@
<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>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 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>
@ -67,13 +67,13 @@
<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&amp;password=fido&amp;ticket=31794"</pre>
<pre>/usr/bin/curl {% url 'helpdesk:api' "hold_ticket" %} --data "user=susan&amp;password=fido&amp;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>&lt;?php
$api = curl_init();
curl_setopt($api, CURLOPT_URL, "{% url 'helpdesk_api' "hold_ticket" %}");
curl_setopt($api, CURLOPT_URL, "{% url 'helpdesk:api' "hold_ticket" %}");
curl_setopt($api, CURLOPT_POST, 1);
curl_setopt($api, CURLOPT_POSTFIELDS, "user=susan&amp;password=fido&amp;ticket=31794");
$result = curl_exec($api);

View File

@ -4,7 +4,7 @@
<thead>
<tr><td colspan='2'>- {% trans "Average number of days until ticket is closed (all tickets): " %}<strong style="color: red;">{{ basic_ticket_stats.average_nbr_days_until_ticket_closed }}</strong>.</td></tr>
<tr><td colspan='2'>- {% trans "Average number of days until ticket is closed (tickets opened in last 60 days): " %}<strong style="color: red;">{{ basic_ticket_stats.average_nbr_days_until_ticket_closed_last_60_days }}</strong>.
{% trans "Click" %} <strong><a href="{% url 'helpdesk_report_index' %}daysuntilticketclosedbymonth">here</a></strong> {% trans "for detailed average by month." %} </td></tr>
{% trans "Click" %} <strong><a href="{% url 'helpdesk:report_index' %}daysuntilticketclosedbymonth">here</a></strong> {% trans "for detailed average by month." %} </td></tr>
<tr><td colspan='2'>- {% trans "Distribution of open tickets, grouped by time period:" %}</td></tr>
<tr><th>{% trans "Days since opened" %}</th><th>{% trans "Number of open tickets" %}</th></tr>
</thead>
@ -12,7 +12,7 @@
{% for entry in basic_ticket_stats.open_ticket_stats %}
<tr>
<th>{{ entry.0 }}</th>
<td><span style="color: {{ entry.2 }};">{% if entry.1 > 0 %}<a href="{% url 'helpdesk_list' %}?{{ entry.3 }}">{{ entry.1 }}</a>{% else %}{{ entry.1 }}{% endif %}</span></td>
<td><span style="color: {{ entry.2 }};">{% if entry.1 > 0 %}<a href="{% url 'helpdesk:list' %}?{{ entry.3 }}">{{ entry.1 }}</a>{% else %}{{ entry.1 }}{% endif %}</span></td>
</tr>
{% endfor %}
</tbody>

View File

@ -6,7 +6,7 @@
</thead>
<tbody>
{% for queue in dash_tickets %}
<tr>{% url 'helpdesk_list' as hdlist %}
<tr>{% url 'helpdesk:list' as hdlist %}
<th><a href='{{ hdlist }}?queue={{ queue.queue }}&status=1&status=2'>{{ queue.name }}</a></th>
<td align="center">{% if queue.open %}<a href='{{ hdlist }}?queue={{ queue.queue }}&status=1&status=2'>{% endif %}{{ queue.open }}{% if queue.open %}</a>{% endif %}</td>
<td align="center">{% if queue.resolved %}<a href='{{ hdlist }}?queue={{ queue.queue }}&status=3'>{% endif %}{{ queue.resolved }}{% if queue.resolved %}</a>{% endif %}</td>

View File

@ -12,7 +12,7 @@
<th><a href='{{ ticket.get_absolute_url }}'>{{ ticket.title }}</a></th>
<td>{{ ticket.queue }}</td>
<td><span title='{{ ticket.created|date:"r" }}'>{{ ticket.created|naturaltime }}</span></td>
<th><a href='{{ ticket.get_absolute_url }}?take'><span class='button button_take'>{% trans "Take" %}</span></a> | <a href='{% url 'helpdesk_delete' ticket.id %}'><span class='button button_delete'>{% trans "Delete" %}</span></a></th>
<th><a href='{{ ticket.get_absolute_url }}?take'><span class='button button_take'>{% trans "Take" %}</span></a> | <a href='{% url 'helpdesk:delete' ticket.id %}'><span class='button button_delete'>{% trans "Delete" %}</span></a></th>
</tr>
{% empty %}
<tr><td colspan='6'>{% trans "There are no unassigned tickets." %}</td></tr>

View File

@ -13,18 +13,18 @@
<div class="collapse navbar-collapse" id="helpdesk-nav-collapse">
{% if helpdesk_settings.HELPDESK_NAVIGATION_ENABLED and user.is_authenticated or user.is_staff %}
<ul class="nav navbar-nav">
<li><a href='{% url 'helpdesk_dashboard' %}'><span class="glyphicon glyphicon-dashboard"></span> <span class="nav-text">{% trans "Dashboard" %}</span></a></li>
<li><a href='{% url 'helpdesk_list' %}'><span class="glyphicon glyphicon-tags"></span> <span class="nav-text">{% trans "Tickets" %}</span></a></li>
<li><a href='{% url 'helpdesk_submit' %}'><span class="glyphicon glyphicon-plus"></span> <span class="nav-text">{% trans "New Ticket" %}</span></a></li>
<li><a href='{% url 'helpdesk_report_index' %}'><span class="glyphicon glyphicon-stats"></span> <span class="nav-text"> {% trans "Stats" %}</span></a></li>
<li><a href='{% url 'helpdesk:dashboard' %}'><span class="glyphicon glyphicon-dashboard"></span> <span class="nav-text">{% trans "Dashboard" %}</span></a></li>
<li><a href='{% url 'helpdesk:list' %}'><span class="glyphicon glyphicon-tags"></span> <span class="nav-text">{% trans "Tickets" %}</span></a></li>
<li><a href='{% url 'helpdesk:submit' %}'><span class="glyphicon glyphicon-plus"></span> <span class="nav-text">{% trans "New Ticket" %}</span></a></li>
<li><a href='{% url 'helpdesk:report_index' %}'><span class="glyphicon glyphicon-stats"></span> <span class="nav-text"> {% trans "Stats" %}</span></a></li>
{% if helpdesk_settings.HELPDESK_KB_ENABLED %}
<li><a href='{% url 'helpdesk_kb_index' %}'><span class="glyphicon glyphicon-tree-deciduous"></span> <span class="nav-text">{% trans "Knowledgebase" %}</span></a></li>
<li><a href='{% url 'helpdesk:kb_index' %}'><span class="glyphicon glyphicon-tree-deciduous"></span> <span class="nav-text">{% trans "Knowledgebase" %}</span></a></li>
{% endif %}
{% if user_saved_queries_ %}
<li class="headerlink dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#"><span class="glyphicon glyphicon-filter"></span> <span class="nav-text">{% trans "Saved Query" %} <b class="caret"></b></span></a>
<ul class="dropdown-menu">
{% 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 %}
(Shared{% ifnotequal user q.user %} by {{ q.user.get_username }}{% endifnotequal %})
{% endif %}</a></li>
@ -34,18 +34,18 @@
{% endif %}
<li class="headerlink dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#"><span class="glyphicon glyphicon-user"></span> <span class="nav-text">{{ user.get_full_name|default:user.get_username }} <b class="caret"></b></span></a>
<ul class="dropdown-menu">
<li><a href='{% url 'helpdesk_user_settings' %}'>{% trans "User Settings" %}</a></li>
<li><a href='{% url 'helpdesk:user_settings' %}'>{% trans "User Settings" %}</a></li>
{% if helpdesk_settings.HELPDESK_SHOW_CHANGE_PASSWORD and user.has_usable_password %}
<li><a href='{% url 'auth_password_change' %}'>{% trans "Change password" %}</a></li>
{% endif %}
<li class="divider"></li>
<li><a href='{% url 'logout' %}'>{#<span class="glyphicon glyphicon-log-out"></span> #}{% trans "Logout" %}</a></li>
<li><a href='{% url 'helpdesk:logout' %}'>{#<span class="glyphicon glyphicon-log-out"></span> #}{% trans "Logout" %}</a></li>
</ul>
</li>
</ul>
{% if not query %}
<form class="navbar-form navbar-left" id='searchform' method='get' action="{% url 'helpdesk_list' %}">
<form class="navbar-form navbar-left" id='searchform' method='get' action="{% url 'helpdesk:list' %}">
<div class="input-group">
<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' />
@ -61,16 +61,16 @@
{# Public menu #}
<ul id="dropdown" class="nav navbar-nav">
{% if helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE %}
<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:dashboard' %}'>{% trans "Dashboard" %}</a></li>
<li><a href='{% url 'helpdesk:submit' %}'>{% trans "Submit a Ticket" %}</a></li>
{% else %}
{% if helpdesk_settings.HELPDESK_SUBMIT_A_TICKET_PUBLIC %}
<li><a href='{% url 'helpdesk_home' %}'>{% trans "Submit a Ticket" %}</a></li>
<li><a href='{% url 'helpdesk:home' %}'>{% trans "Submit a Ticket" %}</a></li>
{% 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 %}
<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 'helpdesk:logout' %}'>{% trans "Logout" %}</a>{% else %}<a href='{% url 'helpdesk:login' %}?next={% if next %}{{ next|escape }}{% else %}{% url 'helpdesk:home' %}{% endif %}'>{% trans "Log In" %}</a>{% endif %}</li>
{% endif %}
</ul>
{% endif %}

View File

@ -72,7 +72,7 @@
<div class="panel-body">
<h2>{% trans "View a Ticket" %}</h2>
<form method='get' action="{% url 'helpdesk_public_view' %}">
<form method='get' action="{% url 'helpdesk:public_view' %}">
<fieldset>
<div class="form-group {% if field.errors %}has-error{% endif %}">
<label for='id_ticket'>{% trans "Ticket" %}</label>

View File

@ -3,7 +3,7 @@
{% block helpdesk_body %}
<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 %}

View File

@ -4,7 +4,7 @@
{% block helpdesk_body %}
{% 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 %}
<div class="col-xs-6">
<div class="panel panel-default">

View File

@ -7,13 +7,13 @@
<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>
<dt><a href='{% url 'helpdesk_rss_user' user.get_username %}'><img src='{% static "helpdesk/rss_icon.png" %}' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "My Open Tickets" %}' border='0' />{% trans "My Open Tickets" %}</a></dt>
<dt><a href='{% url 'helpdesk:rss_user' user.get_username %}'><img src='{% static "helpdesk/rss_icon.png" %}' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "My Open Tickets" %}' border='0' />{% 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>
<dt><a href='{% url 'helpdesk_rss_activity' %}'><img src='{% static "helpdesk/rss_icon.png" %}' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "Latest Activity" %}' border='0' />{% trans "Latest Activity" %}</a></dt>
<dt><a href='{% url 'helpdesk:rss_activity' %}'><img src='{% static "helpdesk/rss_icon.png" %}' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "Latest Activity" %}' border='0' />{% trans "Latest Activity" %}</a></dt>
<dd>{% trans "A summary of all helpdesk activity - including comments, emails, attachments, and more" %}</dd>
<dt><a href='{% url 'helpdesk_rss_unassigned' %}'><img src='{% static "helpdesk/rss_icon.png" %}' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "Unassigned Tickets" %}' border='0' />{% trans "Unassigned Tickets" %}</a></dt>
<dt><a href='{% url 'helpdesk:rss_unassigned' %}'><img src='{% static "helpdesk/rss_icon.png" %}' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "Unassigned Tickets" %}' border='0' />{% 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>
</dl>
@ -28,8 +28,8 @@
{% for queue in queues %}
<tr>
<td>{{ queue.title }}</td>
<td align='center'><a href='{% url 'helpdesk_rss_queue' queue.slug %}'><img src='{% static "helpdesk/rss_icon.png" %}' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "Open Tickets" %}' border='0' /></a></td>
<td align='center'><a href='{% url 'helpdesk_rss_user_queue' user.get_username queue.slug %}'><img src='{% static "helpdesk/rss_icon.png" %}' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "My Open Tickets" %}' border='0' /></a></td>
<td align='center'><a href='{% url 'helpdesk:rss_queue' queue.slug %}'><img src='{% static "helpdesk/rss_icon.png" %}' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "Open Tickets" %}' border='0' /></a></td>
<td align='center'><a href='{% url 'helpdesk:rss_user_queue' user.get_username queue.slug %}'><img src='{% static "helpdesk/rss_icon.png" %}' width='14' height='14' alt='{% trans "RSS Icon" %}' title='{% trans "My Open Tickets" %}' border='0' /></a></td>
{% endfor %}
</tbody>
</table>

View File

@ -8,7 +8,7 @@
<p>{% blocktrans %}The following items can be maintained by you or other superusers:{% endblocktrans %}</p>
<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_presetreply_changelist' %}'>{% trans "Maintain Pre-Set Replies" %}</a></li>
<li><a href='{% url 'admin:helpdesk_kbcategory_changelist' %}'>{% trans "Maintain Knowledgebase Categories" %}</a></li>

View File

@ -69,11 +69,11 @@ function processAddFileClick() {
{% if not followup.public %} <span class='private'>({% trans "Private" %})</span>{% endif %}
{% if helpdesk_settings.HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP %}
{% 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'><img width="60" height="15" title="Edit" alt="Edit" src="{% static "helpdesk/buttons/edit.png" %}"></a>
<a href="{% url 'helpdesk:followup_edit' ticket.id followup.id %}" class='followup-edit'><img width="60" height="15" title="Edit" alt="Edit" src="{% static "helpdesk/buttons/edit.png" %}"></a>
{% endif %}
{% endif %}
{% 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'><img width="60" height="15" title="Delete" alt="Delete" src="{% static "helpdesk/buttons/delete.png" %}"></a>
<a href="{% url 'helpdesk:followup_delete' ticket.id followup.id %}" class='followup-edit'><img width="60" height="15" title="Delete" alt="Delete" src="{% static "helpdesk/buttons/delete.png" %}"></a>
{% endif %}
</div>
{% else %}
@ -82,11 +82,11 @@ function processAddFileClick() {
{{ followup.title }} <span class='byline text-info'>{% if followup.user %}by {{ followup.user }}{% endif %} <span title='{{ followup.date|date:"r" }}'>{{ followup.date|naturaltime }}</span>{% if not followup.public %} <span class='private'>({% trans "Private" %})</span>{% endif %}</span>
{% if helpdesk_settings.HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP %}
{% 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'><img width="60" height="15" title="Edit" alt="Edit" src="{% static "helpdesk/buttons/edit.png" %}"></a>
<a href="{% url 'helpdesk:followup_edit' ticket.id followup.id %}" class='followup-edit'><img width="60" height="15" title="Edit" alt="Edit" src="{% static "helpdesk/buttons/edit.png" %}"></a>
{% endif %}
{% endif %}
{% 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'><img width="60" height="15" title="Delete" alt="Delete" src="{% static "helpdesk/buttons/delete.png" %}"></a>
<a href="{% url 'helpdesk:followup_delete' ticket.id followup.id %}" class='followup-edit'><img width="60" height="15" title="Delete" alt="Delete" src="{% static "helpdesk/buttons/delete.png" %}"></a>
{% endif %}
</div>
{% endif %}
@ -99,7 +99,7 @@ function processAddFileClick() {
{% for attachment in followup.attachment_set.all %}{% if forloop.first %}<div class='attachments'><ul>{% endif %}
<li><a href='{{ attachment.file.url }}'>{{ attachment.filename }}</a> ({{ attachment.mime_type }}, {{ attachment.size|filesizeformat }})
{% if followup.user and request.user == followup.user %}
<a href='{% url 'helpdesk_attachment_del' ticket.id attachment.id %}'>delete</a>
<a href='{% url 'helpdesk:attachment_del' ticket.id attachment.id %}'>delete</a>
{% endif %}
</li>
{% if forloop.last %}</ul></div>{% endif %}

View File

@ -20,12 +20,12 @@
<td>{{ person.display }}</td>
<td>{{ person.can_view }}</td>
<td>{{ person.can_update }}</td>
<td><a href='{% url 'helpdesk_ticket_cc_del' ticket.id person.id %}'>{% trans "Delete" %}</a></td>
<td><a href='{% url 'helpdesk:ticket_cc_del' ticket.id person.id %}'>{% trans "Delete" %}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
<p><a href='{% url 'helpdesk_view' ticket.id %}'>{% blocktrans with ticket.title as ticket_title %}Return to <em>{{ ticket_title }}</em>{% endblocktrans %}</a></p>
<p><a href='{% url 'helpdesk:view' ticket.id %}'>{% blocktrans with ticket.title as ticket_title %}Return to <em>{{ ticket_title }}</em>{% endblocktrans %}</a></p>
{% endblock %}

View File

@ -3,9 +3,9 @@
<table class="table table-hover table-bordered table-striped">
<thead>
<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"><span class="glyphicon glyphicon-pencil"></span> Edit</a>
| <a href='{% url 'helpdesk_delete' ticket.id %}' class="ticket-delete"><span class="glyphicon glyphicon-remove"></span> Delete</a>
{% if ticket.on_hold %} | <a href='{% url 'helpdesk_unhold' ticket.id %}' class="ticket-hold">{% trans "Unhold" %}</a>{% else %} | <a href='{% url 'helpdesk_hold' ticket.id %}' class="ticket-hold">{% trans "Hold" %}</a>{% endif %}
<a href='{% url 'helpdesk:edit' ticket.id %}' class="ticket-edit"><span class="glyphicon glyphicon-pencil"></span> Edit</a>
| <a href='{% url 'helpdesk:delete' ticket.id %}' class="ticket-delete"><span class="glyphicon glyphicon-remove"></span> Delete</a>
{% if ticket.on_hold %} | <a href='{% url 'helpdesk:unhold' ticket.id %}' class="ticket-hold">{% trans "Unhold" %}</a>{% else %} | <a href='{% url 'helpdesk:hold' ticket.id %}' class="ticket-hold">{% trans "Hold" %}</a>{% endif %}
</span></td></tr>
<tr><th colspan='2'>{% blocktrans with ticket.queue as queue %}Queue: {{ queue }}{% endblocktrans %}</th></tr>
</thead>
@ -41,7 +41,7 @@
<tr>
<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 }}'>{% trans "Ignore" %}</a></strong>{% endif %}</td>
<td>{{ ticket.submitter_email }}{% if user.is_superuser %} <strong><a href='{% url 'helpdesk:email_ignore_add' %}?email={{ ticket.submitter_email }}'>{% trans "Ignore" %}</a></strong>{% endif %}</td>
</tr>
<tr>
@ -51,19 +51,19 @@
<tr>
<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>{% trans "Manage" %}</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." %}'>{% trans "Subscribe" %}</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>{% trans "Manage" %}</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." %}'>{% trans "Subscribe" %}</a></strong>{% endif %}</td>
</tr>
<tr>
<th>{% trans "Dependencies" %}</th>
<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 %}
<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 %}'>{% trans "Remove Dependency" %}</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 %}'>{% trans "Remove Dependency" %}</a></li>
{% if forloop.last %}</ul>{% endif %}
{% empty %}
<p>{% trans "This ticket has no dependencies." %}</p>
{% 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." %}">{% trans "Add Dependency" %}</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." %}">{% trans "Add Dependency" %}</a></p>
</td>
</tr>

View File

@ -141,7 +141,7 @@ $(document).ready(function() {
<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>
<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>
@ -162,7 +162,7 @@ $(document).ready(function() {
</div>
<div id="collapseTwo" class="panel-collapse collapse">
<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 }}' />
<dl>
<dt><label for='id_title'>{% trans "Query Name" %}</label></dt>
@ -196,7 +196,7 @@ $(document).ready(function() {
</div>
<div id="collapseThree" class="panel-collapse collapse">
<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'>
{% 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>
@ -216,7 +216,7 @@ $(document).ready(function() {
<div class="row">
{{ 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 class="table table-hover table-bordered table-striped">
<caption>{% trans "Tickets" %}</caption>
<thead>

View File

@ -17,8 +17,9 @@ Assuming 'food' = 'pizza' and 'best_foods' = ['pizza', 'pie', 'cake]:
from django import template
def in_list(value, arg):
return value in ( arg or [] )
return value in (arg or [])
register = template.Library()
register.filter(in_list)

View File

@ -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
django-helpdesk/helpdesk/settings.py
"""
from __future__ import print_function
from django.template import Library
from helpdesk import settings as helpdesk_settings_config
def load_helpdesk_settings(request):
try:
return helpdesk_settings_config
except Exception as e:
import sys
print >> sys.stderr, "'load_helpdesk_settings' template tag (django-helpdesk) crashed with following error:"
print >> sys.stderr, e
print("'load_helpdesk_settings' template tag (django-helpdesk) crashed with following error:",
file=sys.stderr)
print(e, file=sys.stderr)
return ''
register = Library()

View File

@ -17,8 +17,8 @@ def saved_queries(user):
return user_saved_queries
except Exception as e:
import sys
print >> sys.stderr, "'saved_queries' template tag (django-helpdesk) crashed with following error:"
print >> sys.stderr, e
print >> sys.stderr, "'saved_queries' template tag (django-helpdesk) crashed with following error:"
print >> sys.stderr, e
return ''
register = Library()

View File

@ -20,18 +20,6 @@ from django.utils.safestring import mark_safe
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):
if text == '':
return text
@ -40,11 +28,9 @@ def num_to_link(text):
for match in re.finditer(r"(?:[^&]|\b|^)#(\d+)\b", text):
matches.append(match)
for match in ReverseProxy(matches):
start = match.start()
end = match.end()
for match in reversed(matches):
number = match.groups()[0]
url = reverse('helpdesk_view', args=[number])
url = reverse('helpdesk:view', args=[number])
try:
ticket = Ticket.objects.get(id=number)
except Ticket.DoesNotExist:
@ -52,7 +38,8 @@ def num_to_link(text):
if ticket:
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)
register = template.Library()

View File

@ -12,6 +12,7 @@ templatetags/admin_url.py - Very simple template tag allow linking to the
from django import template
from django.contrib.auth import get_user_model
def user_admin_url(action):
user = get_user_model()
try:

View File

@ -1,11 +1,8 @@
# -*- coding: utf-8 -*-
import sys
try:
from django.contrib.auth import get_user_model
except ImportError:
from django.contrib.auth.models import User
else:
User = get_user_model()
from django.contrib.auth import get_user_model
User = get_user_model()
def get_staff_user(username='helpdesk.staff', password='password'):

View File

@ -26,11 +26,11 @@ class TestKBDisabled(TestCase):
from django.core.urlresolvers import NoReverseMatch
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:
response = self.client.get(reverse('helpdesk_dashboard'))
response = self.client.get(reverse('helpdesk:dashboard'))
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)")
else:
raise

View File

@ -75,7 +75,7 @@ class PerQueueStaffMembershipTestCase(TestCase):
# Regular users
for identifier in self.IDENTIFIERS:
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(
len(response.context['unassigned_tickets']),
identifier,
@ -99,7 +99,7 @@ class PerQueueStaffMembershipTestCase(TestCase):
# Superuser
self.client.login(username='superuser', password='superuser')
response = self.client.get(reverse('helpdesk_dashboard'))
response = self.client.get(reverse('helpdesk:dashboard'))
self.assertEqual(
len(response.context['unassigned_tickets']),
3,
@ -132,7 +132,7 @@ class PerQueueStaffMembershipTestCase(TestCase):
# Regular users
for identifier in self.IDENTIFIERS:
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(
len(response.context['tickets']),
identifier * 2,
@ -151,7 +151,7 @@ class PerQueueStaffMembershipTestCase(TestCase):
# Superuser
self.client.login(username='superuser', password='superuser')
response = self.client.get(reverse('helpdesk_list'))
response = self.client.get(reverse('helpdesk:list'))
self.assertEqual(
len(response.context['tickets']),
6,
@ -168,7 +168,7 @@ class PerQueueStaffMembershipTestCase(TestCase):
for identifier in self.IDENTIFIERS:
self.client.login(username='User_%d' % identifier, password=str(identifier))
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
# unassigned and this user only
@ -199,7 +199,7 @@ class PerQueueStaffMembershipTestCase(TestCase):
# Superuser
self.client.login(username='superuser', password='superuser')
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
# three columns: unassigned and both user 1 and user 2

View File

@ -1,9 +1,9 @@
from helpdesk.models import Queue, CustomField, Ticket
from helpdesk.models import Queue, Ticket
from django.test import TestCase
from django.core import mail
from django.test.client import Client
from django.core.urlresolvers import reverse
class PublicActionsTestCase(TestCase):
"""
Tests for public actions:
@ -11,17 +11,28 @@ class PublicActionsTestCase(TestCase):
- Add a followup
- Close resolved case
"""
def setUp(self):
"""
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.ticket = Ticket.objects.create(title='Test Ticket', queue=self.queue, submitter_email='test.submitter@example.com', description='This is a test ticket.')
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.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()
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.assertTemplateNotUsed(response, 'helpdesk/public_view_form.html')
@ -38,7 +49,10 @@ class PublicActionsTestCase(TestCase):
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)
@ -46,7 +60,7 @@ class PublicActionsTestCase(TestCase):
self.assertTemplateNotUsed(response, 'helpdesk/public_view_form.html')
self.assertEqual(ticket.status, Ticket.CLOSED_STATUS)
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.status = old_status

View File

@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
from django.core.urlresolvers import reverse
from django.test import TestCase
from helpdesk.models import Ticket, Queue
from helpdesk.tests.helpers import get_staff_user, reload_urlconf
from helpdesk.models import Queue
from helpdesk.tests.helpers import get_staff_user
class TestSavingSharedQuery(TestCase):
def setUp(self):
@ -12,15 +13,18 @@ class TestSavingSharedQuery(TestCase):
def test_cansavequery(self):
"""Can a query be saved"""
url = reverse('helpdesk_savequery')
url = reverse('helpdesk:savequery')
self.client.login(username=get_staff_user().get_username(),
password='password')
response = self.client.post(url,
data={'title': 'ticket on my queue',
'queue':self.q,
'shared':'on',
'query_encoded':'KGRwMApWZmlsdGVyaW5nCnAxCihkcDIKVnN0YXR1c19faW4KcDMKKGxwNApJMQphSTIKYUkzCmFzc1Zzb3J0aW5nCnA1ClZjcmVhdGVkCnA2CnMu'})
response = self.client.post(
url,
data={
'title': 'ticket on my queue',
'queue': self.q,
'shared': 'on',
'query_encoded':
'KGRwMApWZmlsdGVyaW5nCnAxCihkcDIKVnN0YXR1c19faW4KcDMKKG'
'xwNApJMQphSTIKYUkzCmFzc1Zzb3J0aW5nCnA1ClZjcmVhdGVkCnA2CnMu'
})
self.assertEqual(response.status_code, 302)
self.assertTrue('tickets/?saved_query=1' in response.url)

View File

@ -3,6 +3,7 @@ from django.core.urlresolvers import reverse
from django.test import TestCase
from helpdesk.models import Ticket, Queue
class TestKBDisabled(TestCase):
def setUp(self):
q = Queue(title='Q1', slug='q1')
@ -14,22 +15,19 @@ class TestKBDisabled(TestCase):
def test_ticket_by_id(self):
"""Can a ticket be looked up by its ID"""
from django.core.urlresolvers import NoReverseMatch
# get the ticket from models
t = Ticket.objects.get(id=self.ticket.id)
self.assertEqual(t.title, self.ticket.title)
def test_ticket_by_link(self):
"""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
link = self.ticket.ticket_url
# however instead of using that link, we will exercise 'reverse'
# to lookup/build the URL from the ticket info we have
# http://example.com/helpdesk/view/?ticket=q1-1&email=None
response = self.client.get(reverse('helpdesk_public_view'),
# Instead of using the ticket_for_url link,
# we will exercise 'reverse' to lookup/build the URL
# from the ticket info we have
# http://example.com/helpdesk/view/?ticket=q1-1&email=None
response = self.client.get(reverse('helpdesk:public_view'),
{'ticket': self.ticket.ticket_for_url,
'email':self.ticket.submitter_email})
'email': self.ticket.submitter_email})
self.assertEqual(response.status_code, 200)
def test_ticket_with_changed_queue(self):
@ -38,9 +36,9 @@ class TestKBDisabled(TestCase):
q2 = Queue(title='Q2', slug='q2')
q2.save()
# 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,
'email':self.ticket.submitter_email}
'email': self.ticket.submitter_email}
# Pickup the ticket created in setup() and change its queue
self.ticket.queue = q2
self.ticket.save()

View File

@ -14,13 +14,23 @@ class TicketBasicsTestCase(TestCase):
fixtures = ['emailtemplate.json']
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_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.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_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 = {
'title': 'Test Ticket',
'description': 'Some Test Ticket',
}
'title': 'Test Ticket',
'description': 'Some Test Ticket',
}
self.client = Client()
@ -31,75 +41,85 @@ class TicketBasicsTestCase(TestCase):
self.assertEqual(ticket.ticket_for_url, "q1-%s" % ticket.id)
self.assertEqual(email_count, len(mail.outbox))
def test_create_ticket_public(self):
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)
post_data = {
'title': 'Test ticket title',
'queue': self.queue_public.id,
'submitter_email': 'ticket1.submitter@example.com',
'body': 'Test ticket body',
'priority': 3,
}
'title': 'Test ticket title',
'queue': self.queue_public.id,
'submitter_email': 'ticket1.submitter@example.com',
'body': 'Test ticket body',
'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_url = last_redirect[0]
last_redirect_status = last_redirect[1]
# last_redirect_status = last_redirect[1]
# Ensure we landed on the "View" page.
# 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
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.
self.assertEqual(email_count+3, len(mail.outbox))
self.assertEqual(email_count + 3, len(mail.outbox))
def test_create_ticket_private(self):
email_count = len(mail.outbox)
post_data = {
'title': 'Private ticket test',
'queue': self.queue_private.id,
'submitter_email': 'ticket2.submitter@example.com',
'body': 'Test ticket body',
'priority': 3,
}
'title': 'Private ticket test',
'queue': self.queue_private.id,
'submitter_email': 'ticket2.submitter@example.com',
'body': 'Test ticket body',
'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(email_count, len(mail.outbox))
self.assertContains(response, 'Select a valid choice.')
def test_create_ticket_customfields(self):
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')
custom_field_1 = CustomField.objects.create(name='textfield', label='Text Field', data_type='varchar', max_length=100, ordering=10, required=False, staff_only=False)
queue_custom = Queue.objects.create(
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 = {
'queue': queue_custom.id,
'title': 'Ticket with custom text field',
'submitter_email': 'ticket3.submitter@example.com',
'body': 'Test ticket body',
'priority': 3,
'custom_textfield': 'This is my custom text.',
}
'queue': queue_custom.id,
'title': 'Ticket with custom text field',
'submitter_email': 'ticket3.submitter@example.com',
'body': 'Test ticket body',
'priority': 3,
'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()
last_redirect = response.redirect_chain[-1]
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.
# 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
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.
self.assertEqual(email_count+2, len(mail.outbox))
self.assertEqual(email_count + 2, len(mail.outbox))

5
helpdesk/tests/urls.py Normal file
View File

@ -0,0 +1,5 @@
from django.conf.urls import include, url
urlpatterns = [
url(r'^helpdesk/', include('helpdesk.urls', namespace='helpdesk')),
]

View File

@ -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.
"""
from django.conf import settings
import django
if django.get_version().startswith("1.3"):
from django.conf.urls.defaults import *
else:
from django.conf.urls import *
from django.conf.urls import url
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.views import feeds, staff, public, api, kb
from django.contrib.auth import views as auth_views
from django.views.generic import TemplateView
class DirectTemplateView(TemplateView):
extra_context = None
def get_context_data(self, **kwargs):
context = super(self.__class__, self).get_context_data(**kwargs)
if self.extra_context is not None:
@ -32,161 +29,163 @@ class DirectTemplateView(TemplateView):
context[key] = value
return context
app_name = 'helpdesk'
urlpatterns = [
url(r'^dashboard/$',
staff.dashboard,
name='helpdesk_dashboard'),
name='dashboard'),
url(r'^tickets/$',
staff.ticket_list,
name='helpdesk_list'),
name='list'),
url(r'^tickets/update/$',
staff.mass_update,
name='helpdesk_mass_update'),
name='mass_update'),
url(r'^tickets/submit/$',
staff.create_ticket,
name='helpdesk_submit'),
name='submit'),
url(r'^tickets/(?P<ticket_id>[0-9]+)/$',
staff.view_ticket,
name='helpdesk_view'),
name='view'),
url(r'^tickets/(?P<ticket_id>[0-9]+)/followup_edit/(?P<followup_id>[0-9]+)/$',
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]+)/$',
staff.followup_delete,
name='helpdesk_followup_delete'),
name='followup_delete'),
url(r'^tickets/(?P<ticket_id>[0-9]+)/edit/$',
staff.edit_ticket,
name='helpdesk_edit'),
name='edit'),
url(r'^tickets/(?P<ticket_id>[0-9]+)/update/$',
staff.update_ticket,
name='helpdesk_update'),
name='update'),
url(r'^tickets/(?P<ticket_id>[0-9]+)/delete/$',
staff.delete_ticket,
name='helpdesk_delete'),
name='delete'),
url(r'^tickets/(?P<ticket_id>[0-9]+)/hold/$',
staff.hold_ticket,
name='helpdesk_hold'),
name='hold'),
url(r'^tickets/(?P<ticket_id>[0-9]+)/unhold/$',
staff.unhold_ticket,
name='helpdesk_unhold'),
name='unhold'),
url(r'^tickets/(?P<ticket_id>[0-9]+)/cc/$',
staff.ticket_cc,
name='helpdesk_ticket_cc'),
name='ticket_cc'),
url(r'^tickets/(?P<ticket_id>[0-9]+)/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]+)/$',
staff.ticket_cc_del,
name='helpdesk_ticket_cc_del'),
name='ticket_cc_del'),
url(r'^tickets/(?P<ticket_id>[0-9]+)/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]+)/$',
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]+)/$',
staff.attachment_del,
name='helpdesk_attachment_del'),
name='attachment_del'),
url(r'^raw/(?P<type>\w+)/$',
staff.raw_details,
name='helpdesk_raw'),
name='raw'),
url(r'^rss/$',
staff.rss_list,
name='helpdesk_rss_index'),
name='rss_index'),
url(r'^reports/$',
staff.report_index,
name='helpdesk_report_index'),
name='report_index'),
url(r'^reports/(?P<report>\w+)/$',
staff.run_report,
name='helpdesk_run_report'),
name='run_report'),
url(r'^save_query/$',
staff.save_query,
name='helpdesk_savequery'),
name='savequery'),
url(r'^delete_query/(?P<id>[0-9]+)/$',
staff.delete_saved_query,
name='helpdesk_delete_query'),
name='delete_query'),
url(r'^settings/$',
staff.user_settings,
name='helpdesk_user_settings'),
name='user_settings'),
url(r'^ignore/$',
staff.email_ignore,
name='helpdesk_email_ignore'),
name='email_ignore'),
url(r'^ignore/add/$',
staff.email_ignore_add,
name='helpdesk_email_ignore_add'),
name='email_ignore_add'),
url(r'^ignore/delete/(?P<id>[0-9]+)/$',
staff.email_ignore_del,
name='helpdesk_email_ignore_del'),
name='email_ignore_del'),
]
urlpatterns += [
url(r'^$',
public.homepage,
name='helpdesk_home'),
name='home'),
url(r'^view/$',
public.view_ticket,
name='helpdesk_public_view'),
name='public_view'),
url(r'^change_language/$',
public.change_language,
name='helpdesk_public_change_language'),
name='public_change_language'),
]
urlpatterns += [
url(r'^rss/user/(?P<user_name>[^/]+)/$',
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_-]+)/$',
login_required(feeds.OpenTicketsByUser()),
name='helpdesk_rss_user_queue'),
name='rss_user_queue'),
url(r'^rss/queue/(?P<queue_slug>[A-Za-z0-9_-]+)/$',
login_required(feeds.OpenTicketsByQueue()),
name='helpdesk_rss_queue'),
name='rss_queue'),
url(r'^rss/unassigned/$',
login_required(feeds.UnassignedTickets()),
name='helpdesk_rss_unassigned'),
name='rss_unassigned'),
url(r'^rss/recent_activity/$',
login_required(feeds.RecentFollowUps()),
name='helpdesk_rss_activity'),
name='rss_activity'),
]
urlpatterns += [
url(r'^api/(?P<method>[a-z_-]+)/$',
api.api,
name='helpdesk_api'),
name='api'),
url(r'^login/$',
auth_views.login,
@ -203,31 +202,31 @@ if helpdesk_settings.HELPDESK_KB_ENABLED:
urlpatterns += [
url(r'^kb/$',
kb.index,
name='helpdesk_kb_index'),
name='kb_index'),
url(r'^kb/(?P<item>[0-9]+)/$',
kb.item,
name='helpdesk_kb_item'),
name='kb_item'),
url(r'^kb/(?P<item>[0-9]+)/vote/$',
kb.vote,
name='helpdesk_kb_vote'),
name='kb_vote'),
url(r'^kb/(?P<slug>[A-Za-z0-9_-]+)/$',
kb.category,
name='helpdesk_kb_category'),
name='kb_category'),
]
urlpatterns += [
url(r'^api/$',
TemplateView.as_view(template_name='helpdesk/help_api.html'),
name='helpdesk_api_help'),
name='api_help'),
url(r'^help/context/$',
TemplateView.as_view(template_name='helpdesk/help_context.html'),
name='helpdesk_help_context'),
name='help_context'),
url(r'^system_settings/$',
DirectTemplateView.as_view(template_name='helpdesk/system_settings.html'),
name='helpdesk_system_settings'),
name='system_settings'),
]

View File

@ -11,16 +11,10 @@ The API documentation can be accessed by visiting http://helpdesk/api/help/
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.contrib.auth import get_user_model
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
@ -35,6 +29,8 @@ from helpdesk.models import Ticket, Queue, FollowUp
import warnings
User = get_user_model()
STATUS_OK = 200
STATUS_ERROR = 400
@ -57,11 +53,13 @@ def api(request, method):
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
SEE https://github.com/django-helpdesk/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)
warnings.warn("django-helpdesk API will be removed in January 2016. "
"See https://github.com/django-helpdesk/django-helpdesk/issues/198 for details.",
category=DeprecationWarning)
if method == 'help':
return render(request, template_name='helpdesk/help_api.html')
@ -73,7 +71,7 @@ def api(request, method):
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)
@ -111,10 +109,10 @@ def api_return(status, text='', json=False):
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()]
@ -126,10 +124,11 @@ class API:
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)
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)
@ -141,7 +140,6 @@ class API:
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")
@ -155,7 +153,6 @@ class API:
return api_return(STATUS_OK)
def api_public_hold_ticket(self):
try:
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
@ -167,7 +164,6 @@ class API:
return api_return(STATUS_OK)
def api_public_unhold_ticket(self):
try:
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
@ -179,7 +175,6 @@ class API:
return api_return(STATUS_OK)
def api_public_add_followup(self):
try:
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
@ -201,7 +196,7 @@ class API:
comment=message,
user=self.request.user,
title='Comment Added',
)
)
if public:
f.public = True
@ -220,7 +215,7 @@ class API:
recipients=ticket.submitter_email,
sender=ticket.queue.from_address,
fail_silently=True,
)
)
messages_sent_to.append(ticket.submitter_email)
if public:
@ -232,7 +227,7 @@ class API:
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:
@ -242,7 +237,7 @@ class API:
recipients=ticket.queue.updated_ticket_cc,
sender=ticket.queue.from_address,
fail_silently=True,
)
)
messages_sent_to.append(ticket.queue.updated_ticket_cc)
if (
@ -264,7 +259,6 @@ class API:
return api_return(STATUS_OK)
def api_public_resolve(self):
try:
ticket = Ticket.objects.get(id=self.request.POST.get('ticket', False))
@ -283,13 +277,13 @@ class API:
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)
# subject = '%s %s (Resolved)' % (ticket.ticket, ticket.title)
messages_sent_to = []
@ -300,7 +294,7 @@ class API:
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():
@ -311,7 +305,7 @@ class API:
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:
@ -321,17 +315,22 @@ class API:
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:
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

View File

@ -7,11 +7,7 @@ views/feeds.py - A handful of staff-only RSS feeds to provide ticket details
to feed readers or similar software.
"""
try:
from django.contrib.auth import get_user_model
User = get_user_model()
except ImportError:
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from django.contrib.syndication.views import Feed
from django.core.urlresolvers import reverse
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
User = get_user_model()
class OpenTicketsByUser(Feed):
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") % {
'queue': obj['queue'].title,
'username': obj['user'].get_username(),
}
}
else:
return _("Helpdesk: Open Tickets for %(username)s") % {
'username': obj['user'].get_username(),
}
}
def description(self, obj):
if obj['queue']:
return _("Open and Reopened Tickets in queue %(queue)s for %(username)s") % {
'queue': obj['queue'].title,
'username': obj['user'].get_username(),
}
}
else:
return _("Open and Reopened Tickets for %(username)s") % {
'username': obj['user'].get_username(),
}
}
def link(self, obj):
if obj['queue']:
return u'%s?assigned_to=%s&queue=%s' % (
reverse('helpdesk_list'),
reverse('helpdesk:list'),
obj['user'].id,
obj['queue'].id,
)
)
else:
return u'%s?assigned_to=%s' % (
reverse('helpdesk_list'),
reverse('helpdesk:list'),
obj['user'].id,
)
)
def items(self, obj):
if obj['queue']:
return Ticket.objects.filter(
assigned_to=obj['user']
).filter(
queue=obj['queue']
).filter(
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
)
assigned_to=obj['user']
).filter(
queue=obj['queue']
).filter(
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
)
else:
return Ticket.objects.filter(
assigned_to=obj['user']
).filter(
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
)
assigned_to=obj['user']
).filter(
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
)
def item_pubdate(self, item):
return item.created
@ -101,19 +99,18 @@ class UnassignedTickets(Feed):
title = _('Helpdesk: Unassigned 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):
return Ticket.objects.filter(
assigned_to__isnull=True
).filter(
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
)
assigned_to__isnull=True
).filter(
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
)
def item_pubdate(self, item):
return item.created
def item_author_name(self, item):
if item.assigned_to:
return item.assigned_to.get_username()
@ -127,7 +124,7 @@ class RecentFollowUps(Feed):
title = _('Helpdesk: Recent Followups')
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):
return FollowUp.objects.order_by('-date')[:20]
@ -143,25 +140,25 @@ class OpenTicketsByQueue(Feed):
def title(self, obj):
return _('Helpdesk: Open Tickets in queue %(queue)s') % {
'queue': obj.title,
}
}
def description(self, obj):
return _('Open and Reopened Tickets in queue %(queue)s') % {
'queue': obj.title,
}
}
def link(self, obj):
return '%s?queue=%s' % (
reverse('helpdesk_list'),
reverse('helpdesk:list'),
obj.id,
)
)
def items(self, obj):
return Ticket.objects.filter(
queue=obj
).filter(
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
)
queue=obj
).filter(
Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)
)
def item_pubdate(self, item):
return item.created
@ -171,4 +168,3 @@ class OpenTicketsByQueue(Feed):
return item.assigned_to.get_username()
else:
return _('Unassigned')

View File

@ -8,12 +8,8 @@ views/kb.py - Public-facing knowledgebase views. The knowledgebase is a
resolutions to common problems.
"""
from datetime import datetime
from django.http import HttpResponseRedirect
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.models import KBCategory, KBItem
@ -22,31 +18,28 @@ from helpdesk.models import KBCategory, KBItem
def index(request):
category_list = KBCategory.objects.all()
# TODO: It'd be great to have a list of most popular items here.
return render(request, template_name='helpdesk/kb_index.html',
context = {
'kb_categories': category_list,
'helpdesk_settings': helpdesk_settings,
})
return render(request, 'helpdesk/kb_index.html', {
'kb_categories': category_list,
'helpdesk_settings': helpdesk_settings,
})
def category(request, slug):
category = get_object_or_404(KBCategory, slug__iexact=slug)
items = category.kbitem_set.all()
return render(request, template_name='helpdesk/kb_category.html',
context = {
'category': category,
'items': items,
'helpdesk_settings': helpdesk_settings,
})
return render(request, 'helpdesk/kb_category.html', {
'category': category,
'items': items,
'helpdesk_settings': helpdesk_settings,
})
def item(request, item):
item = get_object_or_404(KBItem, pk=item)
return render(request, template_name='helpdesk/kb_item.html',
context = {
'item': item,
'helpdesk_settings': helpdesk_settings,
})
return render(request, 'helpdesk/kb_item.html', {
'item': item,
'helpdesk_settings': helpdesk_settings,
})
def vote(request, item):
@ -59,4 +52,3 @@ def vote(request, item):
item.save()
return HttpResponseRedirect(item.get_absolute_url())

View File

@ -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
required) views.
"""
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, Http404, HttpResponse
from django.shortcuts import render, get_object_or_404
from django.template import loader, Context, RequestContext
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.utils.translation import ugettext as _
from helpdesk import settings as helpdesk_settings
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
def homepage(request):
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:
if request.user.usersettings.settings.get('login_view_ticketlist', False):
return HttpResponseRedirect(reverse('helpdesk_list'))
return HttpResponseRedirect(reverse('helpdesk:list'))
else:
return HttpResponseRedirect(reverse('helpdesk_dashboard'))
return HttpResponseRedirect(reverse('helpdesk:dashboard'))
except UserSettings.DoesNotExist:
return HttpResponseRedirect(reverse('helpdesk_dashboard'))
return HttpResponseRedirect(reverse('helpdesk:dashboard'))
if request.method == 'POST':
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 text_is_spam(form.cleaned_data['body'], request):
# This submission is spam. Let's not save it.
return render(request, template_name='helpdesk/public_spam.html')
else:
ticket = form.save()
return HttpResponseRedirect('%s?ticket=%s&email=%s'% (
reverse('helpdesk_public_view'),
return HttpResponseRedirect('%s?ticket=%s&email=%s' % (
reverse('helpdesk:public_view'),
ticket.ticket_for_url,
ticket.submitter_email)
)
)
else:
try:
queue = Queue.objects.get(slug=request.GET.get('queue', None))
@ -59,36 +61,38 @@ def homepage(request):
initial_data['submitter_email'] = request.user.email
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()
return render(request, 'helpdesk/public_homepage.html',
{
'form': form,
'helpdesk_settings': helpdesk_settings,
'kb_categories': knowledgebase_categories
})
return render(request, 'helpdesk/public_homepage.html', {
'form': form,
'helpdesk_settings': helpdesk_settings,
'kb_categories': knowledgebase_categories
})
def view_ticket(request):
ticket_req = request.GET.get('ticket', '')
ticket = False
email = request.GET.get('email', '')
error_message = ''
if ticket_req and email:
queue, ticket_id = Ticket.queue_and_id_from_query(ticket_req)
try:
ticket = Ticket.objects.get(id=ticket_id, submitter_email__iexact=email)
except:
ticket = False
except ObjectDoesNotExist:
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:
redirect_url = reverse('helpdesk_view', args=[ticket_id])
redirect_url = reverse('helpdesk:view', args=[ticket_id])
if 'close' in request.GET:
redirect_url += '?close'
return HttpResponseRedirect(redirect_url)
@ -102,7 +106,7 @@ def view_ticket(request):
'public': 1,
'title': ticket.title,
'comment': _('Submitter accepted resolution and closed ticket'),
}
}
if ticket.assigned_to:
request.POST['owner'] = ticket.assigned_to.id
request.GET = {}
@ -112,27 +116,18 @@ def view_ticket(request):
# redirect user back to this ticket if possible.
redirect_url = ''
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',
{
'ticket': ticket,
'helpdesk_settings': helpdesk_settings,
'next': redirect_url,
})
return render(request, 'helpdesk/public_view_ticket.html', {
'ticket': ticket,
'helpdesk_settings': helpdesk_settings,
'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):
return_to = ''
if 'return_to' in request.GET:
return_to = request.GET['return_to']
return render(request, template_name='helpdesk/public_change_language.html',
context = {'next': return_to})
return render(request, 'helpdesk/public_change_language.html', {'next': return_to})

File diff suppressed because it is too large Load Diff

View File

@ -38,6 +38,27 @@ class QuickDjangoTest(object):
'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):
self.apps = args
# 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
"""
settings.configure(DEBUG = True,
DATABASE_ENGINE = 'sqlite3',
DATABASE_NAME = os.path.join(self.DIRNAME, 'database.db'),
INSTALLED_APPS = self.INSTALLED_APPS + self.apps
)
settings.configure(DEBUG=True,
DATABASE_ENGINE='sqlite3',
DATABASE_NAME=os.path.join(self.DIRNAME, 'database.db'),
INSTALLED_APPS=self.INSTALLED_APPS + self.apps
)
from django.test.simple import run_tests
failures = run_tests(self.apps, verbosity=1)
if failures:
@ -77,8 +98,8 @@ class QuickDjangoTest(object):
"""
settings.configure(
DEBUG = True,
DATABASES = {
DEBUG=True,
DATABASES={
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(self.DIRNAME, 'database.db'),
@ -88,10 +109,11 @@ class QuickDjangoTest(object):
'PORT': '',
}
},
INSTALLED_APPS = self.INSTALLED_APPS + self.apps,
MIDDLEWARE_CLASSES = self.MIDDLEWARE_CLASSES,
ROOT_URLCONF = self.apps[0] + '.urls',
STATIC_URL = '/static/'
INSTALLED_APPS=self.INSTALLED_APPS + self.apps,
MIDDLEWARE_CLASSES=self.MIDDLEWARE_CLASSES,
ROOT_URLCONF='helpdesk.tests.urls',
STATIC_URL='/static/',
TEMPLATES=self.TEMPLATES
)
# compatibility with django 1.8 downwards
@ -129,4 +151,3 @@ if __name__ == '__main__':
parser.add_argument('apps', nargs='+', type=str)
args = parser.parse_args()
QuickDjangoTest(*args.apps)

View File

@ -1,4 +1,4 @@
Django>1.6
Django>=1.8
django-bootstrap-form>=3.1,<4
email-reply-parser
django-markdown-deux

View File

@ -6,7 +6,7 @@ from distutils.util import convert_path
from fnmatch import fnmatchcase
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
# of replicating them:
@ -137,10 +137,13 @@ setup(
"Topic :: Office/Business",
"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_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',
packages=find_packages(),
package_data=find_package_data("helpdesk", only_in_packages=False),