├── helpdesk ├── __init__.py ├── views │ ├── __init__.py │ └── kb.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── create_usersettings.py │ │ └── create_escalation_exclusions.py ├── migrations │ ├── __init__.py │ ├── 0004_initial_data_import.py │ ├── 0002_socks_proxy.py │ └── 0003_populate_usersettings.py ├── templatetags │ ├── __init__.py │ ├── user_admin_url.py │ ├── in_list.py │ ├── load_helpdesk_settings.py │ ├── saved_queries.py │ └── ticket_to_link.py ├── south_migrations │ ├── __init__.py │ └── 0011_populate_usersettings.py ├── static │ └── helpdesk │ │ ├── helpdesk-print.css │ │ ├── rss_icon.png │ │ ├── buttons │ │ ├── edit.png │ │ ├── take.png │ │ ├── accept.png │ │ ├── delete.png │ │ ├── button_template.psd │ │ └── button_template.txt │ │ ├── priorities │ │ ├── priority1.png │ │ ├── priority2.png │ │ ├── priority3.png │ │ ├── priority4.png │ │ ├── priority5.png │ │ └── readme.txt │ │ ├── jquery-smoothness-theme │ │ └── images │ │ │ ├── ui-icons_222222_256x240.png │ │ │ ├── ui-icons_2e83ff_256x240.png │ │ │ ├── ui-icons_454545_256x240.png │ │ │ ├── ui-icons_888888_256x240.png │ │ │ ├── ui-icons_cd0a0a_256x240.png │ │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ │ └── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ ├── filter.js │ │ ├── jquery.jqplot │ │ ├── plugins │ │ │ ├── jqplot.ciParser.min.js │ │ │ ├── jqplot.mobile.min.js │ │ │ ├── jqplot.trendline.min.js │ │ │ ├── jqplot.canvasAxisLabelRenderer.min.js │ │ │ ├── jqplot.canvasAxisTickRenderer.min.js │ │ │ ├── jqplot.blockRenderer.min.js │ │ │ ├── jqplot.json2.min.js │ │ │ ├── jqplot.ohlcRenderer.min.js │ │ │ ├── jqplot.dragable.min.js │ │ │ ├── jqplot.pointLabels.min.js │ │ │ └── jqplot.enhancedLegendRenderer.min.js │ │ └── jquery.jqplot.min.css │ │ └── helpdesk-extend.css ├── templates │ └── helpdesk │ │ ├── rss │ │ ├── recent_activity_description.html │ │ ├── ticket_title.html │ │ ├── ticket_description.html │ │ └── recent_activity_title.html │ │ ├── attribution.html │ │ ├── ru │ │ ├── email_text_footer.txt │ │ └── email_html_base.html │ │ ├── en │ │ ├── email_text_footer.txt │ │ └── email_html_base.html │ │ ├── de │ │ ├── email_text_footer.txt │ │ └── email_html_base.html │ │ ├── fr │ │ ├── email_text_footer.txt │ │ └── email_html_base.html │ │ ├── it │ │ ├── email_text_footer.txt │ │ └── email_html_base.html │ │ ├── registration │ │ ├── logged_out.html │ │ └── login.html │ │ ├── ticket_dependency_del.html │ │ ├── ticket_cc_del.html │ │ ├── delete_ticket.html │ │ ├── user_settings.html │ │ ├── public_spam.html │ │ ├── email_ignore_del.html │ │ ├── kb_index.html │ │ ├── public_change_language.html │ │ ├── public_view_form.html │ │ ├── confirm_delete_saved_query.html │ │ ├── kb_category.html │ │ ├── ticket_cc_add.html │ │ ├── ticket_dependency_add.html │ │ ├── include │ │ ├── tickets.html │ │ ├── summary.html │ │ ├── unassigned.html │ │ └── stats.html │ │ ├── help_base.html │ │ ├── system_settings.html │ │ ├── email_ignore_add.html │ │ ├── public_base.html │ │ ├── debug.html │ │ ├── dashboard.html │ │ ├── ticket_cc_list.html │ │ ├── create_ticket.html │ │ ├── email_ignore_list.html │ │ ├── followup_edit.html │ │ ├── report_index.html │ │ ├── kb_item.html │ │ ├── edit_ticket.html │ │ ├── rss_list.html │ │ ├── public_view_ticket.html │ │ ├── public_homepage.html │ │ ├── base.html │ │ ├── ticket_desc_table.html │ │ ├── report_output.html │ │ └── navigation.html ├── locale │ ├── ar │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── cs │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── de │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── el │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── en │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── es │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── fi │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── fr │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── hr │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── hu │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── it │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── pl │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── ru │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── sv │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── es_CO │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── es_MX │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── fa_IR │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── nb_NO │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── pt_BR │ │ └── LC_MESSAGES │ │ │ └── django.mo │ └── zh_CN │ │ └── LC_MESSAGES │ │ └── django.mo ├── apps.py ├── tests │ ├── __init__.py │ ├── helpers.py │ ├── navigation.py │ ├── public_actions.py │ └── ticket_submission.py ├── poll_helpdesk_email_queues.sh ├── admin.py └── settings.py ├── .gitignore ├── requirements.txt ├── .tx └── config ├── .travis.yml ├── docs ├── api.rst ├── custom_fields.rst ├── spam.rst ├── configuration.rst ├── index.rst ├── contributing.rst ├── install.rst └── Makefile ├── MANIFEST.in ├── AUTHORS ├── LICENSE ├── CHANGELOG ├── quicktest.py ├── LICENSE.3RDPARTY ├── README.rst └── setup.py /helpdesk/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helpdesk/views/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helpdesk/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helpdesk/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helpdesk/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helpdesk/south_migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helpdesk/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/helpdesk-print.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/rss/recent_activity_description.html: -------------------------------------------------------------------------------- 1 | {{ obj.comment }} 2 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/rss/ticket_title.html: -------------------------------------------------------------------------------- 1 | {{ obj.ticket }} {{ obj.title }} ({{ obj.created }}) 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | dist 3 | django_helpdesk.egg-info 4 | docs/html/* 5 | docs/doctrees/* 6 | .project 7 | .pydevproject -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django>1.4 2 | django-bootstrap-form>=3.1,<4 3 | email-reply-parser 4 | django-markdown-deux 5 | simplejson 6 | -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/rss_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/rss_icon.png -------------------------------------------------------------------------------- /helpdesk/locale/ar/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/ar/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/cs/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/cs/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/de/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/de/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/el/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/el/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/en/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/en/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/es/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/es/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/fi/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/fi/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/fr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/fr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/hr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/hr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/hu/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/hu/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/it/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/it/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/pl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/pl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/ru/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/ru/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/sv/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/sv/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/buttons/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/buttons/edit.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/buttons/take.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/buttons/take.png -------------------------------------------------------------------------------- /helpdesk/locale/es_CO/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/es_CO/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/es_MX/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/es_MX/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/fa_IR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/fa_IR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/nb_NO/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/nb_NO/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/pt_BR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/pt_BR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/locale/zh_CN/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/locale/zh_CN/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/buttons/accept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/buttons/accept.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/buttons/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/buttons/delete.png -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/attribution.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% trans "django-helpdesk." %} 3 | -------------------------------------------------------------------------------- /helpdesk/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | class HelpdeskConfig(AppConfig): 4 | name = 'helpdesk' 5 | verbose_name = "Helpdesk" 6 | 7 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/rss/ticket_description.html: -------------------------------------------------------------------------------- 1 | {{ obj.description }}{% if obj.submitter_email %} (Submitted by {{ obj.submitter_email }}){% endif %} 2 | -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/priorities/priority1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/priorities/priority1.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/priorities/priority2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/priorities/priority2.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/priorities/priority3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/priorities/priority3.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/priorities/priority4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/priorities/priority4.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/priorities/priority5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/priorities/priority5.png -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/rss/recent_activity_title.html: -------------------------------------------------------------------------------- 1 | {{ obj.title }} by {{ obj.user }} (on {{ obj.ticket.ticket }} {{ obj.ticket.title }}, {{ obj.date }}) 2 | -------------------------------------------------------------------------------- /helpdesk/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from helpdesk.tests.ticket_submission import * 2 | from helpdesk.tests.public_actions import * 3 | from helpdesk.tests.navigation import * -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/buttons/button_template.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/buttons/button_template.psd -------------------------------------------------------------------------------- /.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | 4 | [django-helpdesk.core] 5 | file_filter = helpdesk/locale//LC_MESSAGES/django.po 6 | source_lang = en 7 | 8 | -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelhelmick/django-helpdesk/master/helpdesk/static/helpdesk/jquery-smoothness-theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/ru/email_text_footer.txt: -------------------------------------------------------------------------------- 1 | С уважением, 2 | 3 | {{ queue.title }}{% if queue.email_address %} 4 | {{ queue.email_address }}{% endif %} 5 | 6 | Это письмо было отправлено Вам, как нашему клиенту. Пожалуйста, сообщите нам, если Вы получили это письмо по ошибке. 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | env: 5 | - DJANGO=1.5.10 6 | - DJANGO=1.6.7 7 | - DJANGO=1.7 8 | install: 9 | - pip install argparse 10 | - pip install -q Django==$DJANGO 11 | - pip install -q -r requirements.txt 12 | script: python quicktest.py helpdesk 13 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | Ticket API 2 | ========== 3 | 4 | django-helpdesk includes an API accessible via HTTP POST requests, allowing you to create and alter tickets from 3rd party software and systems. 5 | 6 | For usage instructions and command syntax, see the file ``templates/helpdesk/api_help.html``, or visit http://helpdesk/api/help/. 7 | -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/priorities/readme.txt: -------------------------------------------------------------------------------- 1 | * Typeface: Delicious, Roman, 14pt, Sharp Anti-Aliasing 2 | http://www.josbuivenga.demon.nl/delicious.html 3 | * Icons: Diagona (16px versions) 4 | http://www.pinvoke.com/ - I purchased these before they were creative-commons, I believe this gives me rights to use them without attribution. 5 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/en/email_text_footer.txt: -------------------------------------------------------------------------------- 1 | Regards, 2 | 3 | {{ queue.title }}{% if queue.email_address %} 4 | {{ queue.email_address }}{% endif %} 5 | 6 | This e-mail was sent to you as a user of our support service, in accordance with our privacy policy. Please advise us if you believe you have received this e-mail in error. 7 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README 2 | include UPGRADE 3 | include LICENSE* 4 | include CHANGELOG 5 | include requirements.txt 6 | 7 | recursive-include helpdesk/static/helpdesk * 8 | recursive-include helpdesk/locale *.po *.mo 9 | recursive-include helpdesk/templates * 10 | recursive-include helpdesk/fixtures *.json 11 | recursive-include docs/html * 12 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/de/email_text_footer.txt: -------------------------------------------------------------------------------- 1 | Beste Grüße, 2 | 3 | {{ queue.title }}{% if queue.email_address %} 4 | {{ queue.email_address }}{% endif %} 5 | 6 | Diese E-Mail wurde, im Einklang mit unserer Privacy-Policy an Sie als Benutzer unseres Support-Dienstes gesendet. Bitte teilen Sie uns mit wenn Sie diese E-Mail nicht hätten bekommen sollen. 7 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/fr/email_text_footer.txt: -------------------------------------------------------------------------------- 1 | Cordialement, 2 | 3 | {{ queue.title }}{% if queue.email_address %} 4 | {{ queue.email_address }}{% endif %} 5 | 6 | Ce courriel vous a été envoyé en tant qu'utilisateur de notre service de support, en accord avec notre politique de confidentialité. Merci de nous informer si vous pensez que ce message ne vous était pas destiné. 7 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/it/email_text_footer.txt: -------------------------------------------------------------------------------- 1 | Cordiali saluti, 2 | 3 | {{ queue.title }}{% if queue.email_address %} 4 | {{ queue.email_address }}{% endif %} 5 | 6 | Questa email vi è stata inviata in quanto utenti del nostro servizio di supporto clienti, in conformità con la nostra policy sulla privacy. Vi preghiamo di informarci se pensate di aver ricevuto questa email per errore. 7 | -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/buttons/button_template.txt: -------------------------------------------------------------------------------- 1 | * Use this template to create new buttons 2 | * Typeface: Delicious, Roman, 12pt, Sharp Anti-Aliasing 3 | http://www.josbuivenga.demon.nl/delicious.html 4 | * Icons: Diagona (10px versions) 5 | http://www.pinvoke.com/ - I purchased these before they were creative-commons, I believe this gives me rights to use them without attribution. 6 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/registration/logged_out.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/public_base.html" %}{% load i18n %} 2 | {% block helpdesk_title %}{% trans "Logged Out" %}{% endblock %} 3 | 4 | {% block helpdesk_body %}{% blocktrans %} 5 |

Logged Out

6 | 7 |

Thanks for being here. Hopefully you've helped resolve a few tickets and make the world a better place.

8 | 9 | {% endblocktrans %}{% endblock %} 10 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | django-helpdesk was originally written by Ross Poulton. Since publishing the 2 | code a number of people have made some fantastic improvements and provided 3 | bug fixes and updates as the Django codebase has moved on and caused small 4 | portions of this application to break. 5 | 6 | To these people, and any more, my sincere thanks: 7 | 8 | Andreas Kotowicz 9 | Chris Etcp 10 | David Clymer 11 | Loe Spee 12 | Maxim Litnitskiy 13 | Nikolay Panov 14 | -------------------------------------------------------------------------------- /helpdesk/poll_helpdesk_email_queues.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | # don't forget to add this script to the /etc/crontab: 5 | # 6 | # */1 * * * * username /home/username/django/project/poll_helpdesk_email_queues.sh >> /tmp/foo.log 2>&1 7 | 8 | # set your django and project paths here 9 | PATHTODJANGO="/home/username/django/libraries/lib/python" 10 | PATHTOPROJECT="/home/username/django/project/" 11 | 12 | 13 | export PYTHONPATH=$PYTHONPATH:$PATHTODJANGO:$PATHTOPROJECT: 14 | 15 | cd $PATHTOPROJECT 16 | /usr/bin/python manage.py get_email 17 | 18 | 19 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/ticket_dependency_del.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n %}{% load url from future %} 2 | 3 | {% block helpdesk_title %}{% trans "Delete Ticket Dependency" %}{% endblock %} 4 | 5 | {% block helpdesk_body %}{% blocktrans %} 6 |

Delete Ticket Dependency

7 | 8 |

Are you sure you wish to remove the dependency on this ticket?

9 | {% endblocktrans %} 10 | 11 |

{% trans "Don't Delete" %}

12 | 13 |
{% csrf_token %}
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /docs/custom_fields.rst: -------------------------------------------------------------------------------- 1 | Custom Fields 2 | ============= 3 | 4 | django-helpdesk supports custom fields on the ``Ticket`` model. These fields are created by using the Django administration tool, and are shown on both the public and staff submission forms. You can use most Django field types including text, integer, boolean, and list. 5 | 6 | The demo at http://django-helpdesk-demo.herokuapp.com contains an example of each type of custom field, including a mix of mandatory and optional fields. 7 | 8 | Custom fields are relatively inefficient, and you cannot search by them. They can be useful for tracking extra information that your organisation needs but that isn't supported out of the box. 9 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/ticket_cc_del.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n %}{% load url from future %} 2 | 3 | {% block helpdesk_title %}{% trans "Delete Ticket CC" %}{% endblock %} 4 | 5 | {% block helpdesk_body %}{% blocktrans with cc.email_address as email_address %} 6 |

Delete Ticket CC

7 | 8 |

Are you sure you wish to delete this email address ({{ email_address }}) from the CC list for this ticket? They will stop receiving updates.

9 | {% endblocktrans %} 10 | 11 |

{% trans "Don't Delete" %}

12 | 13 |
{% csrf_token %}
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/delete_ticket.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n %} 2 | 3 | {% block helpdesk_title %}{% trans "Delete Ticket" %}{% endblock %} 4 | 5 | {% block helpdesk_body %} 6 |

{% trans "Delete Ticket" %}

7 | 8 |

{% blocktrans with ticket.title as ticket_title %}Are you sure you want to delete this ticket ({{ ticket_title }})? All traces of the ticket, including followups, attachments, and updates will be irreversibly removed.{% endblocktrans %}

9 | 10 |

{% trans "No, Don't Delete It" %}

11 | 12 |
{% csrf_token %}
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/user_settings.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n bootstrap %}{% load url from future %} 2 | 3 | {% block helpdesk_title %}{% trans "Change User Settings" %}{% endblock %} 4 | 5 | {% block helpdesk_body %} 6 |

{% trans "User Settings" %}

7 | 8 |

{% blocktrans %}Use the following options to change the way your helpdesk system works for you. These settings do not impact any other user.{% endblocktrans %}

9 | 10 |
11 | {% csrf_token %} 12 | {{ form|bootstrap }} 13 |
14 | 15 |
16 |
17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /helpdesk/templatetags/user_admin_url.py: -------------------------------------------------------------------------------- 1 | """ 2 | django-helpdesk - A Django powered ticket tracker for small enterprise. 3 | 4 | (c) Copyright 2008 Jutda. All Rights Reserved. See LICENSE for details. 5 | 6 | templatetags/admin_url.py - Very simple template tag allow linking to the 7 | right auth user model urls. 8 | 9 | {% url 'changelist'|user_admin_url %} 10 | """ 11 | 12 | from django import template 13 | from django.contrib.auth import get_user_model 14 | 15 | def user_admin_url(action): 16 | user = get_user_model() 17 | return 'admin:%s_%s_%s' % ( 18 | user._meta.app_label, user._meta.module_name.lower(), 19 | action) 20 | 21 | register = template.Library() 22 | register.filter(user_admin_url) 23 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/public_spam.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/public_base.html" %}{% load i18n %} 2 | 3 | {% block helpdesk_body %} 4 |

{% trans "Unable To Open Ticket" %}

5 |

{% trans "Sorry, but there has been an error trying to submit your ticket." %}

6 |

{% blocktrans %}Our system has marked your submission as spam, so we are 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.{% endblocktrans %}

7 |

{% blocktrans %}We are sorry for any inconvenience, however this check is required to avoid our helpdesk resources being overloaded by spammers.{% endblocktrans %}

8 | {% endblock helpdesk_body %} 9 | -------------------------------------------------------------------------------- /helpdesk/templatetags/in_list.py: -------------------------------------------------------------------------------- 1 | """ 2 | django-helpdesk - A Django powered ticket tracker for small enterprise. 3 | 4 | (c) Copyright 2008 Jutda. All Rights Reserved. See LICENSE for details. 5 | 6 | templatetags/in_list.py - Very simple template tag to allow us to use the 7 | equivilent of 'if x in y' in templates. eg: 8 | 9 | Assuming 'food' = 'pizza' and 'best_foods' = ['pizza', 'pie', 'cake]: 10 | 11 | {% if food|in_list:best_foods %} 12 | You've selected one of our favourite foods! 13 | {% else %} 14 | Your food isn't one of our favourites. 15 | {% endif %} 16 | """ 17 | 18 | from django import template 19 | 20 | def in_list(value, arg): 21 | return value in ( arg or [] ) 22 | 23 | register = template.Library() 24 | register.filter(in_list) 25 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/ru/email_html_base.html: -------------------------------------------------------------------------------- 1 |

{% block header %}Техническая поддержка{% endblock %}

2 | 3 | {% block content %}{% endblock %} 4 | 5 |

С уважением,

6 | 7 |

{{ queue.title }}{% if queue.email_address %}
{{ queue.email_address }}{% endif %}

8 | 9 |

Это письмо было отправлено Вам, как нашему клиенту. Пожалуйста, сообщите нам, если Вы получили это письмо по ошибке.

10 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/email_ignore_del.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n %} 2 | 3 | {% block helpdesk_title %}{% trans "Delete Ignored E-Mail Address" %}{% endblock %} 4 | 5 | {% block helpdesk_body %} 6 |

{% trans "Un-Ignore E-Mail Address" %}

7 | 8 |

{% blocktrans with ignore.email_address as email_address %}Are you sure you wish to stop removing this email address ({{ email_address }}) and allow their e-mails to automatically create tickets in your system? You can re-add this e-mail address at any time.{% endblocktrans %}

9 | 10 |

{% trans "Keep Ignoring It" %}

11 | 12 |
{% csrf_token %}
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/filter.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $("#filterBuilderButton").click(function() { 3 | var boxName = "#filterBox" + $("#filterBuilderSelect").val(); 4 | $(boxName).slideDown(); 5 | return false; 6 | }); 7 | $(".filterBuilderRemove").click(function() { 8 | var boxName = "#" + $(this).parents(".filterBox").attr('id'); 9 | $(boxName).slideUp(); 10 | $(boxName).children("input:text").each(function() { 11 | $(this).val(""); 12 | }); 13 | $(boxName).children("input:checkbox").each(function() { 14 | this.checked = false; 15 | }); 16 | $(boxName).children("select").each(function() { 17 | this.selectedIndex = -1; 18 | }); 19 | return false; 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery.jqplot/plugins/jqplot.ciParser.min.js: -------------------------------------------------------------------------------- 1 | /* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com 2 | jsDate | (c) 2010-2013 Chris Leonello 3 | */(function(a){a.jqplot.ciParser=function(g,l){var m=[],o,n,h,f,e,c;if(typeof(g)=="string"){g=a.jqplot.JSON.parse(g,d)}else{if(typeof(g)=="object"){for(e in g){for(h=0;h=0){i=/^\/Date\((-?[0-9]+)\)\/$/.exec(k);if(i){return parseInt(i[1],10)}}return k}}for(var b in g){o=[];n=g[b];switch(b){case"PriceTicks":for(h=0;h> sys.stderr, "'load_helpdesk_settings' template tag (django-helpdesk) crashed with following error:" 17 | print >> sys.stderr, e 18 | return '' 19 | 20 | register = Library() 21 | register.filter('load_helpdesk_settings', load_helpdesk_settings) 22 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/en/email_html_base.html: -------------------------------------------------------------------------------- 1 |

{% block header %}Helpdesk{% endblock %}

2 | 3 | {% block content %}{% endblock %} 4 | 5 |

Regards,

6 | 7 |

{{ queue.title }}{% if queue.email_address %}
{{ queue.email_address }}{% endif %}

8 | 9 |

This e-mail was sent to you as a user of our support service, in accordance with our privacy policy. Please advise us if you believe you have received this e-mail in error.

10 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/kb_index.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/public_base.html" %}{% load i18n %} 2 | 3 | {% block helpdesk_body %} 4 |

{% trans "Knowledgebase" %}

5 | 6 |

{% trans "We have listed a number of knowledgebase articles for your perusal in the following categories. Please check to see if any of these articles address your problem prior to opening a support ticket." %}

7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for category in kb_categories %} 15 | 16 | 17 | {% endfor %} 18 | 19 |
{% trans "Knowledgebase Categories" %}
{% trans "Category" %}
{{ category.title }}
{{ category.description }}
20 | 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/public_change_language.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/public_base.html" %}{% load i18n %} 2 | {% block helpdesk_title %}{% trans "View a Ticket" %}{% endblock %} 3 | 4 | {% block helpdesk_body %} 5 |

{% trans "Change the display language" %}

6 |
7 | {% csrf_token %} 8 | 9 | 15 | 16 |
17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/de/email_html_base.html: -------------------------------------------------------------------------------- 1 |

{% block header %}Helpdesk{% endblock %}

2 | 3 | {% block content %}{% endblock %} 4 | 5 |

Beste Grüße,

6 | 7 |

{{ queue.title }}{% if queue.email_address %}
{{ queue.email_address }}{% endif %}

8 | 9 |

Diese E-Mail wurde, im Einklang mit unserer Privacy-Policy an Sie als Benutzer unseres Support-Dienstes gesendet. Bitte teilen Sie uns mit wenn Sie diese E-Mail nicht hätten bekommen sollen.

10 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/public_view_form.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/public_base.html" %}{% load i18n %}{% load url from future %} 2 | 3 | {% block helpdesk_body %} 4 |

{% trans "View a Ticket" %}

5 | 6 |
7 | 8 | {% if error_message %}

{% trans "Error:" %} {{ error_message }}

{% endif %} 9 | 10 |
11 |
12 |
13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | {% csrf_token %}
22 | 23 | 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/fr/email_html_base.html: -------------------------------------------------------------------------------- 1 |

{% block header %}Helpdesk{% endblock %}

2 | 3 | {% block content %}{% endblock %} 4 | 5 |

Cordialement,

6 | 7 |

{{ queue.title }}{% if queue.email_address %}
{{ queue.email_address }}{% endif %}

8 | 9 |

Ce courriel vous a été envoyé en tant qu'utilisateur de notre service de support, en accord avec notre politique de confidentialité. Merci de nous informer si vous pensez que ce message ne vous était pas destiné.

10 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/it/email_html_base.html: -------------------------------------------------------------------------------- 1 |

{% block header %}Helpdesk{% endblock %}

2 | 3 | {% block content %}{% endblock %} 4 | 5 |

Cordiali saluti,

6 | 7 |

{{ queue.title }}{% if queue.email_address %}
{{ queue.email_address }}{% endif %}

8 | 9 |

Questa email vi è stata inviata in quanto utenti del nostro servizio di supporto clienti, in conformità con la nostra policy sulla privacy. Vi preghiamo di informarci se pensate di aver ricevuto questa email per errore.

10 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/confirm_delete_saved_query.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n %} 2 | 3 | {% block helpdesk_title %}{% trans "Delete Saved Query" %}{% endblock %} 4 | 5 | {% block helpdesk_body %} 6 |

{% trans "Delete Query" %}

7 | 8 |

{% blocktrans with query.title as query_title %}Are you sure you want to delete this saved filter ({{ query_title }})? To re-create it, you will need to manually re-filter your ticket listing.{% endblocktrans %}

9 | 10 | {% if query.shared %} 11 |

{% blocktrans %}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.{% endblocktrans %}

12 | {% endif %} 13 | 14 |

{% trans "No, Don't Delete It" %}

15 | 16 |
{% csrf_token %}
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /helpdesk/templatetags/saved_queries.py: -------------------------------------------------------------------------------- 1 | """ 2 | django-helpdesk - A Django powered ticket tracker for small enterprise. 3 | 4 | templatetags/saved_queries.py - This template tag returns previously saved 5 | queries. Therefore you don't need to modify 6 | any views. 7 | """ 8 | 9 | from django.template import Library 10 | from django.db.models import Q 11 | from helpdesk.models import SavedSearch 12 | 13 | 14 | def saved_queries(user): 15 | try: 16 | user_saved_queries = SavedSearch.objects.filter(Q(user=user) | Q(shared__exact=True)) 17 | return user_saved_queries 18 | except Exception, e: 19 | import sys 20 | print >> sys.stderr, "'saved_queries' template tag (django-helpdesk) crashed with following error:" 21 | print >> sys.stderr, e 22 | return '' 23 | 24 | register = Library() 25 | register.filter('saved_queries', saved_queries) 26 | -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery.jqplot/plugins/jqplot.mobile.min.js: -------------------------------------------------------------------------------- 1 | /* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com 2 | jsDate | (c) 2010-2013 Chris Leonello 3 | */(function(b){function a(e,d,c){this.bindCustomEvents=function(){this.eventCanvas._elem.bind("vclick",{plot:this},this.onClick);this.eventCanvas._elem.bind("dblclick",{plot:this},this.onDblClick);this.eventCanvas._elem.bind("taphold",{plot:this},this.onDblClick);this.eventCanvas._elem.bind("vmousedown",{plot:this},this.onMouseDown);this.eventCanvas._elem.bind("vmousemove",{plot:this},this.onMouseMove);this.eventCanvas._elem.bind("mouseenter",{plot:this},this.onMouseEnter);this.eventCanvas._elem.bind("mouseleave",{plot:this},this.onMouseLeave);if(this.captureRightClick){this.eventCanvas._elem.bind("vmouseup",{plot:this},this.onRightClick);this.eventCanvas._elem.get(0).oncontextmenu=function(){return false}}else{this.eventCanvas._elem.bind("vmouseup",{plot:this},this.onMouseUp)}};this.plugins.mobile=true}b.jqplot.postInitHooks.push(a)})(jQuery); -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/kb_category.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/public_base.html" %}{% load i18n humanize %} 2 | 3 | {% block helpdesk_body %} 4 |

{% blocktrans with category.title as kbcat %}Knowledgebase Category: {{ kbcat }}{% endblocktrans %}

5 | 6 |

{% blocktrans with category.title as kbcat %}You are viewing all items in the {{ kbcat }} category.{% endblocktrans %}

7 | 8 |

{{ category.description }}

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% for item in items %} 17 | 18 | 19 | {% endfor %} 20 | 21 |
{% blocktrans with category.title as kbcat %}Knowledgebase Category: {{ kbcat }}{% endblocktrans %}
{% trans "Article" %}
{{ item.title }}Rating: {{ item.score }}Last Update: {{ item.last_updated|naturaltime }}
{{ item.question }}
22 | 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/ticket_cc_add.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n %}{% load url from future %} 2 | 3 | {% block helpdesk_title %}{% trans "Add Ticket CC" %}{% endblock %} 4 | 5 | {% block helpdesk_body %}{% blocktrans %} 6 |

Add Ticket CC

7 | 8 |

To automatically send an email to a user or e-mail address when this ticket is updated, select the user or enter an e-mail address below.

{% endblocktrans %} 9 | 10 |
11 | 12 |
13 |
{% for field in form %} 14 |
15 |
{{ field }}
16 | {% if field.errors %}
{{ field.errors }}
{% endif %} 17 | {% if field.help_text %}
{{ field.help_text }}
{% endif %} 18 | {% endfor %}
19 |
20 | 21 | 22 | 23 | {% csrf_token %}
24 | 25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/ticket_dependency_add.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n %}{% load url from future %} 2 | 3 | {% block helpdesk_title %}{% trans "Add Ticket Dependency" %}{% endblock %} 4 | 5 | {% block helpdesk_body %}{% blocktrans %} 6 |

Add Ticket Dependency

7 | 8 |

Adding a dependency will stop you resolving this ticket until the dependent ticket has been resolved or closed.

{% endblocktrans %} 9 | 10 |
11 | 12 |
13 |
{% for field in form %} 14 |
15 |
{{ field }}
16 | {% if field.errors %}
{{ field.errors }}
{% endif %} 17 | {% if field.help_text %}
{{ field.help_text }}
{% endif %} 18 | {% endfor %}
19 |
20 | 21 | 22 | 23 | {% csrf_token %}
24 | 25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /helpdesk/tests/helpers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sys 3 | try: 4 | from django.contrib.auth import get_user_model 5 | except ImportError: 6 | from django.contrib.auth.models import User 7 | else: 8 | User = get_user_model() 9 | 10 | 11 | def get_staff_user(username='helpdesk.staff', password='password'): 12 | try: 13 | user = User.objects.get(username=username) 14 | except User.DoesNotExist: 15 | user = User.objects.create_user(username=username, password=password, email='staff@example.com') 16 | user.is_staff = True 17 | user.save() 18 | else: 19 | user.set_password(password) 20 | user.save() 21 | return user 22 | 23 | 24 | def reload_urlconf(urlconf=None): 25 | if urlconf is None: 26 | from django.conf import settings 27 | 28 | urlconf = settings.ROOT_URLCONF 29 | if urlconf in sys.modules: 30 | from django.core.urlresolvers import clear_url_caches 31 | 32 | reload(sys.modules[urlconf]) 33 | clear_url_caches() 34 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/include/tickets.html: -------------------------------------------------------------------------------- 1 | {% load i18n humanize %}{% load url from future %} 2 | {% if ticket_list_caption %} 3 | {% endif %} 4 | 5 | 6 | 7 | 8 | {% for ticket in ticket_list %} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% empty %}{% if ticket_list_empty_message %} 18 | 19 | {% endif %}{% endfor %} 20 | 21 |
{{ ticket_list_caption }}
#{% trans "Pr" %}{% trans "Title" %}{% trans "Queue" %}{% trans "Status" %}{% trans "Last Update" %}
{{ ticket.ticket }}{{ ticket.priority }}{{ ticket.title }}{{ ticket.queue }}{{ ticket.get_status }}{{ ticket.modified|naturaltime }}
{{ ticket_list_empty_message }}
22 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/include/summary.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% for queue in dash_tickets %} 9 | {% url 'helpdesk_list' as hdlist %} 10 | 11 | 12 | 13 | 14 | 15 | {% endfor %} 16 | 17 |
{% trans "Helpdesk Summary" %}
{% trans "Queue" %}{% trans "Open" %}{% trans "Resolved" %}{% trans "Closed" %}
{{ queue.name }}{% if queue.open %}{% endif %}{{ queue.open }}{% if queue.open %}{% endif %}{% if queue.resolved %}{% endif %}{{ queue.resolved }}{% if queue.resolved %}{% endif %}{% if queue.closed %}{% endif %}{{ queue.closed }}{% if queue.closed %}{% endif %}
-------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/public_base.html" %}{% load i18n bootstrap %} 2 | {% block helpdesk_title %}{% trans "Helpdesk Login" %}{% endblock %} 3 | 4 | {% block helpdesk_body %} 5 | 6 | {% if request.user.is_authenticated %} 7 | 8 | {% else %} 9 |
10 |
11 | 12 |

Login

13 | 14 |

{% trans "To log in simply enter your username and password below." %}

15 | 16 |
17 | {% if form.errors %}

{% trans "Your username and password didn't match. Please try again." %}

{% endif %} 18 | {{ form|bootstrap }} 19 |
20 | 21 |
22 | 23 | {% csrf_token %} 24 |
25 |
26 |
27 |
28 |
29 | {% endif %} 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /docs/spam.rst: -------------------------------------------------------------------------------- 1 | Spam Filtering 2 | ============== 3 | 4 | django-helpdesk includes a copy of ``akismet.py`` by `Michael Foord `_, which lets incoming ticket submissions be automatically checked against either the `Akismet `_ or `TypePad Anti-Spam `_ services. 5 | 6 | To enable this functionality, sign up for an API key with one of these two services. 7 | 8 | Akismet 9 | ~~~~~~~ 10 | 11 | * Sign up at http://akismet.com/ 12 | * Save your API key in ``settings.py`` as ``AKISMET_API_KEY`` 13 | 14 | **Note**: Akismet is only free for personal use. Paid commercial accounts are available. 15 | 16 | TypePad AntiSpam 17 | ~~~~~~~~~~~~~~~~ 18 | * Sign up at http://antispam.typepad.com/ 19 | * Save your API key in ``settings.py`` as ``TYPEPAD_ANTISPAM_API_KEY`` 20 | 21 | This service is free to use, within their terms and conditions. 22 | 23 | If you have either of these settings enabled, the spam filtering will be done automatically. If you have *both* settings configured, TypePad will be used instead of Akismet. 24 | 25 | 26 | Example 27 | ~~~~~~~ 28 | 29 | A sample configuration in ``settings.py`` may be:: 30 | 31 | TYPEPAD_ANTISPAM_API_KEY = 'abc123' 32 | 33 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/help_base.html: -------------------------------------------------------------------------------- 1 | {% load url from future %} 2 | 3 | 4 | 40 | {% block title %}django-helpdesk Help{% endblock %} 41 | 42 | 43 |

{% block heading %}django-helpdesk Help{% endblock %}

44 | 45 | {% block content %}{% endblock %} 46 | 47 | 48 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/system_settings.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n %}{% load url from future %}{% load user_admin_url %} 2 | 3 | {% block helpdesk_title %}{% trans "Change System Settings" %}{% endblock %} 4 | 5 | {% block helpdesk_body %} 6 |

{% trans "System Settings" %}

7 | 8 |

{% blocktrans %}The following items can be maintained by you or other superusers:{% endblocktrans %}

9 | 10 | 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/include/unassigned.html: -------------------------------------------------------------------------------- 1 | {% load i18n humanize %}{% load url from future %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% for ticket in unassigned_tickets %} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% empty %} 18 | 19 | {% endfor %} 20 | 21 |
{% trans "Unassigned Tickets" %} {% trans "(pick up a ticket if you start to work on it)" %}
#{% trans "Pr" %}{% trans "Title" %}{% trans "Queue" %}{% trans "Created" %} 
{{ ticket.ticket }}{{ ticket.priority }}{{ ticket.title }}{{ ticket.queue }}{{ ticket.created|naturaltime }}{% trans "Take" %} | {% trans "Delete" %}
{% trans "There are no unassigned tickets." %}
22 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/email_ignore_add.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n %} 2 | 3 | {% block helpdesk_title %}{% trans "Ignore E-Mail Address" %}{% endblock %} 4 | 5 | {% block helpdesk_body %} 6 |

{% trans "Ignore E-Mail Address" %}

7 | 8 |

{% blocktrans %}To ignore an e-mail address and prevent any emails from that address creating tickets automatically, enter the e-mail address below.{% endblocktrans %}

9 | 10 |

{% blocktrans %}You can either enter a whole e-mail address such as email@domain.com or a portion of an e-mail address with a wildcard, such as *@domain.com or user@*.{% endblocktrans %}

11 | 12 |
13 | 14 |
15 |
{% for field in form %} 16 |
17 |
{{ field }}
18 | {% if field.errors %}
{{ field.errors }}
{% endif %} 19 | {% if field.help_text %}
{{ field.help_text }}
{% endif %} 20 | {% endfor %}
21 |
22 | 23 | 24 | 25 | {% csrf_token %}
26 | 27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /helpdesk/migrations/0004_initial_data_import.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | import os 5 | from sys import path 6 | 7 | from django.db import models, migrations 8 | from django.core import serializers 9 | 10 | fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures')) 11 | fixture_filename = 'emailtemplate.json' 12 | 13 | def deserialize_fixture(): 14 | fixture_file = os.path.join(fixture_dir, fixture_filename) 15 | 16 | with open(fixture_file, 'rb') as fixture: 17 | return list(serializers.deserialize('json', fixture, ignorenonexistent=True)) 18 | 19 | 20 | def load_fixture(apps, schema_editor): 21 | objects = deserialize_fixture() 22 | 23 | for obj in objects: 24 | obj.save() 25 | 26 | 27 | def unload_fixture(apps, schema_editor): 28 | "Delete all EmailTemplate objects" 29 | 30 | objects = deserialize_fixture() 31 | 32 | EmailTemplate = apps.get_model("helpdesk", "emailtemplate") 33 | EmailTemplate.objects.filter(pk__in=[ obj.object.pk for obj in objects ]).delete() 34 | 35 | 36 | class Migration(migrations.Migration): 37 | 38 | dependencies = [ 39 | ('helpdesk', '0003_populate_usersettings'), 40 | ] 41 | 42 | operations = [ 43 | migrations.RunPython(load_fixture, reverse_code=unload_fixture), 44 | ] 45 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/public_base.html: -------------------------------------------------------------------------------- 1 | {% load i18n %}{% load url from future %} 2 | {% load load_helpdesk_settings %} 3 | {% with request|load_helpdesk_settings as helpdesk_settings %} 4 | 5 | 6 | {% block helpdesk_title %}{% trans "Helpdesk" %}{% endblock %} 7 | 8 | 9 | 10 | 11 | {% block helpdesk_head %}{% endblock %} 12 | 13 | 14 |
15 | 21 |
22 | {% block helpdesk_body %}{% endblock %} 23 |
24 | 27 |
{% include "helpdesk/debug.html" %} 28 | 29 | 30 | {% endwith %} 31 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/include/stats.html: -------------------------------------------------------------------------------- 1 | {% load i18n %}{% load url from future %} 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | {% for entry in basic_ticket_stats.open_ticket_stats %} 13 | 14 | 15 | 16 | 17 | {% endfor %} 18 | 19 |
{% trans "Current Ticket Stats" %}
- {% trans "Average number of days until ticket is closed (all tickets): " %}{{ basic_ticket_stats.average_nbr_days_until_ticket_closed }}.
- {% trans "Average number of days until ticket is closed (tickets opened in last 60 days): " %}{{ basic_ticket_stats.average_nbr_days_until_ticket_closed_last_60_days }}. 7 | {% trans "Click" %} here {% trans "for detailed average by month." %}
- {% trans "Distribution of open tickets, grouped by time period:" %}
{% trans "Days since opened" %}{% trans "Number of open tickets" %}
{{ entry.0 }}{% if entry.1 > 0 %}{{ entry.1 }}{% else %}{{ entry.1 }}{% endif %}
-------------------------------------------------------------------------------- /helpdesk/management/commands/create_usersettings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | django-helpdesk - A Django powered ticket tracker for small enterprise. 4 | 5 | See LICENSE for details. 6 | 7 | create_usersettings.py - Easy way to create helpdesk-specific settings for 8 | users who don't yet have them. 9 | """ 10 | 11 | from django.utils.translation import ugettext as _ 12 | from django.core.management.base import BaseCommand 13 | try: 14 | from django.contrib.auth import get_user_model 15 | User = get_user_model() 16 | except ImportError: 17 | from django.contrib.auth.models import User 18 | 19 | from helpdesk.models import UserSettings 20 | from helpdesk.settings import DEFAULT_USER_SETTINGS 21 | 22 | class Command(BaseCommand): 23 | "create_usersettings command" 24 | 25 | help = _('Check for user without django-helpdesk UserSettings ' 26 | 'and create settings if required. Uses ' 27 | 'settings.DEFAULT_USER_SETTINGS which can be overridden to ' 28 | 'suit your situation.') 29 | 30 | def handle(self, *args, **options): 31 | "handle command line" 32 | for u in User.objects.all(): 33 | try: 34 | s = UserSettings.objects.get(user=u) 35 | except UserSettings.DoesNotExist: 36 | s = UserSettings(user=u, settings=DEFAULT_USER_SETTINGS) 37 | s.save() 38 | -------------------------------------------------------------------------------- /helpdesk/migrations/0002_socks_proxy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('helpdesk', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='queue', 16 | name='socks_proxy_host', 17 | field=models.GenericIPAddressField(help_text='Socks proxy IP address. Default: 127.0.0.1', null=True, verbose_name='Socks Proxy Host', blank=True), 18 | preserve_default=True, 19 | ), 20 | migrations.AddField( 21 | model_name='queue', 22 | name='socks_proxy_port', 23 | field=models.IntegerField(help_text='Socks proxy port number. Default: 9150 (default TOR port)', null=True, verbose_name='Socks Proxy Port', blank=True), 24 | preserve_default=True, 25 | ), 26 | migrations.AddField( 27 | model_name='queue', 28 | name='socks_proxy_type', 29 | field=models.CharField(choices=[(b'socks4', 'SOCKS4'), (b'socks5', 'SOCKS5')], max_length=8, blank=True, help_text='SOCKS4 or SOCKS5 allows you to proxy your connections through a SOCKS server.', null=True, verbose_name='Socks Proxy Type'), 30 | preserve_default=True, 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/debug.html: -------------------------------------------------------------------------------- 1 | {% if debug %} 2 |
3 |

Queries

4 |

5 | {{ sql_queries|length }} Quer{{ sql_queries|pluralize:"y,ies" }} 6 | {% ifnotequal sql_queries|length 0 %} 7 | (Show) 8 | {% endifnotequal %} 9 |

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% for query in sql_queries %} 23 | 24 | 25 | 26 | {% endfor %} 27 | 28 | 29 |
30 | {% endif %} 31 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/dashboard.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n %} 2 | {% block helpdesk_title %}{% trans "Helpdesk Dashboard" %}{% endblock %} 3 | 4 | {% block helpdesk_body %} 5 | 6 |
7 |

{% trans "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." %}

8 |
9 | 10 | {% include 'helpdesk/include/summary.html' %} 11 | 12 | {% include 'helpdesk/include/stats.html' %} 13 | 14 | {% if all_tickets_reported_by_current_user %} 15 | {% trans "All Tickets submitted by you" as ticket_list_caption %} 16 | {% include 'helpdesk/include/tickets.html' with ticket_list=all_tickets_reported_by_current_user ticket_list_empty_message="" %} 17 | {% endif %} 18 | 19 | {% trans "Open Tickets assigned to you (you are working on this ticket)" as ticket_list_caption %} 20 | {% trans "You have no tickets assigned to you." as no_assigned_tickets %} 21 | {% include 'helpdesk/include/tickets.html' with ticket_list=user_tickets ticket_list_empty_message=no_assigned_tickets %} 22 | 23 | {% include 'helpdesk/include/unassigned.html' %} 24 | 25 | {% if user_tickets_closed_resolved %} 26 | {% trans "Closed & resolved Tickets you used to work on" as ticket_list_caption %} 27 | {% include 'helpdesk/include/tickets.html' with ticket_list=user_tickets_closed_resolved ticket_list_empty_message="" %} 28 | {% endif %} 29 | 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/helpdesk-extend.css: -------------------------------------------------------------------------------- 1 | /* 2 | Bootstrap overrides 3 | */ 4 | 5 | .thumbnail.filterBox { 6 | display: none; 7 | float: left; 8 | border: solid #ccc 1px; 9 | padding: 10px; 10 | margin: 4px; 11 | max-width: 24%; 12 | min-height: 200px; 13 | } 14 | 15 | .thumbnail.filterBoxShow { 16 | display: block; 17 | } 18 | 19 | .filterBox label { 20 | clear: both; 21 | display: block; 22 | } 23 | 24 | .filterBox .filterHelp { 25 | color: #aaa; 26 | font-size: 0.8em; 27 | clear: both; 28 | } 29 | 30 | #searchtabs {margin-bottom: 20px;} 31 | 32 | .row_tablehead, table.table caption {background-color: #dbd5d9;} 33 | table.table caption {height: 2em; line-height: 2em; font-weight: bold;} 34 | table.ticket-stats caption {color: #fbff00; font-style: italic;} 35 | table.ticket-stats tbody th, table.ticket-stats tbody tr {padding-left: 20px} 36 | 37 | .errorlist {list-style: none;} 38 | .errorlist {padding: 0;} 39 | .has-error .input-group input, .has-error .input-group select, .has-error .input-group textarea {border-color: #b94a48} 40 | 41 | #helpdesk-nav-collapse #searchform { 42 | padding-top: 0; 43 | } 44 | #ticket-description {background-color: #FCF8E3;} 45 | .followup.well {background-color: #f4f5ff;} 46 | /* 47 | Add your custom styles here 48 | */ 49 | #footer { 50 | border-top: 2px solid #AAAAAA; 51 | margin-top: 20px; 52 | padding: 10px 0; 53 | } 54 | #helpdesk-body {padding-top: 100px;} 55 | img.brand {padding-right: 30px;} -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/ticket_cc_list.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n %}{% load url from future %} 2 | 3 | {% block helpdesk_title %}{% trans "Ticket CC Settings" %}{% endblock %} 4 | 5 | {% block helpdesk_body %}{% blocktrans with ticket.title as ticket_title and ticket.id as ticket_id %} 6 |

Ticket CC Settings

7 | 8 |

The following people will receive an e-mail whenever {{ ticket_title }} is updated. Some people can also view or edit the ticket via the public ticket views.

9 | 10 |

You can add a new e-mail address to the list or delete any of the items below as required.

{% endblocktrans %} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for person in copies_to %} 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% endfor %} 26 | 27 |
{% trans "Ticket CC List" %}
{% trans "E-Mail Address" %}{% trans "View?" %}{% trans "Update?" %}{% trans "Delete" %}
{{ person.display }}{{ person.can_view }}{{ person.can_update }}{% trans "Delete" %}
28 | 29 |

{% blocktrans with ticket.title as ticket_title %}Return to {{ ticket_title }}{% endblocktrans %}

30 | 31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /helpdesk/tests/navigation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.core.urlresolvers import reverse 3 | from django.test import TestCase 4 | 5 | from helpdesk.tests.helpers import get_staff_user, reload_urlconf 6 | 7 | 8 | class TestKBDisabled(TestCase): 9 | def setUp(self): 10 | from helpdesk import settings 11 | 12 | self.HELPDESK_KB_ENABLED = settings.HELPDESK_KB_ENABLED 13 | if self.HELPDESK_KB_ENABLED: 14 | settings.HELPDESK_KB_ENABLED = False 15 | reload_urlconf() 16 | 17 | def tearDown(self): 18 | from helpdesk import settings 19 | 20 | if self.HELPDESK_KB_ENABLED: 21 | settings.HELPDESK_KB_ENABLED = True 22 | reload_urlconf() 23 | 24 | def test_navigation(self): 25 | """Test proper rendering of navigation.html by accessing the dashboard""" 26 | from django.core.urlresolvers import NoReverseMatch 27 | 28 | self.client.login(username=get_staff_user().get_username(), password='password') 29 | self.assertRaises(NoReverseMatch, reverse, 'helpdesk_kb_index') 30 | try: 31 | response = self.client.get(reverse('helpdesk_dashboard')) 32 | except NoReverseMatch, e: 33 | if 'helpdesk_kb_index' in e.message: 34 | self.fail("Please verify any unchecked references to helpdesk_kb_index (start with navigation.html)") 35 | else: 36 | raise 37 | else: 38 | self.assertEqual(response.status_code, 200) 39 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/create_ticket.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n bootstrap %} 2 | 3 | {% block helpdesk_title %}{% trans "Create Ticket" %}{% endblock %} 4 | 5 | {% block helpdesk_body %} 6 |
7 |
8 | 9 |
10 |

{% trans "Submit a Ticket" %}

11 |

{% trans "Unless otherwise stated, all fields are required." %} {% trans "Please provide as descriptive a title and description as possible." %}

12 | 13 |
14 |
15 | {{ form|bootstrap }} 16 | {% comment %}{% for field in form %} 17 | {% if field.is_hidden %} 18 | {{ field }} 19 | {% else %} 20 |
{% if not field.field.required %} {% trans "(Optional)" %}{% endif %}
21 |
{{ field }}
22 | {% if field.errors %}
{{ field.errors }}
{% endif %} 23 | {% if field.help_text %}
{% trans field.help_text %}
{% endif %} 24 | {% endif %} 25 | {% endfor %} 26 | {% endcomment %} 27 | 28 |
29 | 30 |
31 |
32 | 33 | {% csrf_token %}
34 |
35 |
36 |
37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/email_ignore_list.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n %}{% load url from future %} 2 | 3 | {% block helpdesk_title %}{% trans "Ignored E-Mail Addresses" %}{% endblock %} 4 | 5 | {% block helpdesk_body %}{% blocktrans %} 6 |

Ignored E-Mail Addresses

7 | 8 |

The following e-mail addresses are currently being ignored by the incoming e-mail processor. You can add a new e-mail address to the list or delete any of the items below as required.

{% endblocktrans %} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% for ignore in ignore_list %} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% endfor %} 26 | 27 |
{% trans "Ignored E-Mail Addresses" %}
{% trans "Name" %}{% trans "E-Mail Address" %}{% trans "Date Added" %}{% trans "Queues" %}{% trans "Keep in mailbox?" %}{% trans "Delete" %}
{{ ignore.name }}{{ ignore.email_address }}{{ ignore.date }}{% for queue in ignore.queues.all %}{{ queue.slug }}{% if not forloop.last %}, {% endif %}{% empty %}{% trans "All" %}{% endfor %}{% if ignore.keep_in_mailbox %}{% trans "Keep" %}{% endif %}{% trans "Delete" %}
28 | 29 |

{% trans "Note: If the 'Keep' option is not selected, emails sent from that address will be deleted permanently." %}

30 | 31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/followup_edit.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n %} 2 | {% block helpdesk_title %}{% trans "Edit followup" %}{% endblock %} 3 | 4 | {% block helpdesk_head %} 5 | 10 | {% endblock helpdesk_head %} 11 | 12 | {% block helpdesk_body %} 13 | 14 | {% include "helpdesk/ticket_desc_table.html" %} 15 | 16 |

{% trans "Edit FollowUp" %}

17 |
18 | {{ form.non_field_errors }} 19 |
20 |
21 |
22 |
{{ form.ticket }}
23 |
24 |
{{ form.title }}
25 |
26 |
{{ form.comment }}
27 |
28 |
{{ form.public }}
29 |

Public tickets are viewable by the submitter and all staff, but non-public tickets can only be seen by staff.

30 |
31 |
{{ form.new_status }}
32 |

If the status was changed, what was it changed to?

33 |
34 |
35 |

{% csrf_token %} 36 |
37 | {% endblock helpdesk_body %} -------------------------------------------------------------------------------- /helpdesk/migrations/0003_populate_usersettings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.contrib.auth import get_user_model 5 | from django.db import models, migrations 6 | 7 | from helpdesk.settings import DEFAULT_USER_SETTINGS 8 | 9 | 10 | def picke_settings(data): 11 | """Pickling as defined at migration's creation time""" 12 | import cPickle 13 | from helpdesk.lib import b64encode 14 | return b64encode(cPickle.dumps(data)) 15 | 16 | 17 | # https://docs.djangoproject.com/en/1.7/topics/migrations/#data-migrations 18 | def populate_usersettings(apps, schema_editor): 19 | """Create a UserSettings entry for each existing user. 20 | This will only happen once (at install time, or at upgrade) 21 | when the UserSettings model doesn't already exist.""" 22 | 23 | _User = get_user_model() 24 | User = apps.get_model(_User._meta.app_label, _User._meta.model_name) 25 | 26 | # Import historical version of models 27 | UserSettings = apps.get_model("helpdesk", "UserSettings") 28 | 29 | settings_pickled = picke_settings(DEFAULT_USER_SETTINGS) 30 | 31 | for u in User.objects.all(): 32 | try: 33 | UserSettings.objects.get(user=u) 34 | except UserSettings.DoesNotExist: 35 | UserSettings.objects.create(user=u, settings_pickled=settings_pickled) 36 | 37 | 38 | noop = lambda *args, **kwargs: None 39 | 40 | class Migration(migrations.Migration): 41 | 42 | dependencies = [ 43 | ('helpdesk', '0002_socks_proxy'), 44 | ] 45 | 46 | operations = [ 47 | migrations.RunPython(populate_usersettings, reverse_code=noop), 48 | ] 49 | 50 | 51 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/report_index.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n %} 2 | 3 | {% block helpdesk_title %}{% trans "Reports & Statistics" %}{% endblock %} 4 | 5 | {% block helpdesk_body %} 6 |

{% trans "Reports & Statistics" %}

7 | 8 | {% ifequal number_tickets 0 %} 9 |

{% trans "You haven't created any tickets yet, so you cannot run any reports." %}

10 | {% else %} 11 | 12 | 31 | 32 | {% endifequal %}{% endblock %} 33 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/kb_item.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/public_base.html" %}{% load i18n %}{% load markdown_deux_tags %} 2 | 3 | {% block helpdesk_body %} 4 |

{% blocktrans with item.title as item %}Knowledgebase: {{ item }}{% endblocktrans %}

5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
{{ item.title }}
{{ item.question }}
{{ item.answer|markdown }}
13 | 14 |

{% blocktrans with item.category.title as category_title and item.category.get_absolute_url as category_url %}View other {{ category_title }} articles, or continue viewing other knowledgebase articles.{% endblocktrans %}

15 | 16 |

{% trans "Feedback" %}

17 | 18 |

{% trans "We give our users an opportunity to vote for items that they believe have helped them out, in order for us to better serve future customers. We would appreciate your feedback on this article. Did you find it useful?" %}

19 | 20 | 24 | 25 |

{% trans "The results of voting by other readers of this article are below:" %}

26 | 27 |
    28 |
  • {% blocktrans with item.recommendations as recommendations %}Recommendations: {{ recommendations }}{% endblocktrans %}
  • 29 |
  • {% blocktrans with item.votes as votes %}Votes: {{ votes }}{% endblocktrans %}
  • 30 |
  • {% blocktrans with item.score as score %}Overall Rating: {{ score }}{% endblocktrans %}
  • 31 |
32 | 33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /helpdesk/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from helpdesk.models import Queue, Ticket, FollowUp, PreSetReply, KBCategory 3 | from helpdesk.models import EscalationExclusion, EmailTemplate, KBItem 4 | from helpdesk.models import TicketChange, Attachment, IgnoreEmail 5 | from helpdesk.models import CustomField 6 | 7 | class QueueAdmin(admin.ModelAdmin): 8 | list_display = ('title', 'slug', 'email_address', 'locale') 9 | 10 | class TicketAdmin(admin.ModelAdmin): 11 | list_display = ('title', 'status', 'assigned_to', 'submitter_email',) 12 | date_hierarchy = 'created' 13 | list_filter = ('assigned_to', 'status', ) 14 | 15 | class TicketChangeInline(admin.StackedInline): 16 | model = TicketChange 17 | 18 | class AttachmentInline(admin.StackedInline): 19 | model = Attachment 20 | 21 | class FollowUpAdmin(admin.ModelAdmin): 22 | inlines = [TicketChangeInline, AttachmentInline] 23 | 24 | class KBItemAdmin(admin.ModelAdmin): 25 | list_display = ('category', 'title', 'last_updated',) 26 | list_display_links = ('title',) 27 | 28 | class CustomFieldAdmin(admin.ModelAdmin): 29 | list_display = ('name', 'label', 'data_type') 30 | 31 | class EmailTemplateAdmin(admin.ModelAdmin): 32 | list_display = ('template_name', 'heading', 'locale') 33 | list_filter = ('locale', ) 34 | 35 | admin.site.register(Ticket, TicketAdmin) 36 | admin.site.register(Queue, QueueAdmin) 37 | admin.site.register(FollowUp, FollowUpAdmin) 38 | admin.site.register(PreSetReply) 39 | admin.site.register(EscalationExclusion) 40 | admin.site.register(EmailTemplate, EmailTemplateAdmin) 41 | admin.site.register(KBCategory) 42 | admin.site.register(KBItem, KBItemAdmin) 43 | admin.site.register(IgnoreEmail) 44 | admin.site.register(CustomField, CustomFieldAdmin) 45 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/edit_ticket.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n bootstrap %} 2 | 3 | {% block helpdesk_title %}{% trans "Edit Ticket" %}{% endblock %} 4 | 5 | {% block helpdesk_body %} 6 |
7 |
8 | 9 |

{% trans "Edit a Ticket" %}

10 | 11 |

{% trans "Unless otherwise stated, all fields are required." %} {% trans "Please provide as descriptive a title and description as possible." %}

12 | 13 |

{% trans "Note" %}: {% blocktrans %}Editing a ticket does not 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.{% endblocktrans %}

14 | 15 |
16 |
17 | {{ form|bootstrap }} 18 | {% comment %} 19 | {% for field in form %} 20 | {% if field.is_hidden %} 21 | {{ field }} 22 | {% else %} 23 |
{% if not field.field.required %} {% trans "(Optional)" %}{% endif %}
24 |
{{ field }}
25 | {% if field.errors %}
{{ field.errors }}
{% endif %} 26 | {% if field.help_text %}
{{ field.help_text }}
{% endif %} 27 | {% endif %} 28 | {% endfor %} 29 | 30 | {% endcomment %} 31 |
32 | 33 |
34 |
35 | 36 | {% csrf_token %}
37 |
38 |
39 |
40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008, Ross Poulton (Trading as Jutda) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of Ross Poulton, Jutda, nor the names of any 15 | of its contributors may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | EXCEPTIONS 30 | 31 | This software is distributed with some third-party software which is not distributed under the above license. See LICENSE.3RDPARTY for further details. 32 | -------------------------------------------------------------------------------- /helpdesk/templatetags/ticket_to_link.py: -------------------------------------------------------------------------------- 1 | """ 2 | django-helpdesk - A Django powered ticket tracker for small enterprise. 3 | 4 | (c) Copyright 2008 Jutda. All Rights Reserved. See LICENSE for details. 5 | 6 | templatetags/ticket_to_link.py - Used in ticket comments to allow wiki-style 7 | linking to other tickets. Including text such 8 | as '#3180' in a comment automatically links 9 | that text to ticket number 3180, with styling 10 | to show the status of that ticket (eg a closed 11 | ticket would have a strikethrough). 12 | """ 13 | 14 | import re 15 | 16 | from django import template 17 | from django.core.urlresolvers import reverse 18 | from django.utils.safestring import mark_safe 19 | 20 | from helpdesk.models import Ticket 21 | 22 | 23 | class ReverseProxy: 24 | def __init__(self, sequence): 25 | self.sequence = sequence 26 | 27 | def __iter__(self): 28 | length = len(self.sequence) 29 | i = length 30 | while i > 0: 31 | i = i - 1 32 | yield self.sequence[i] 33 | 34 | 35 | def num_to_link(text): 36 | if text == '': 37 | return text 38 | 39 | matches = [] 40 | for match in re.finditer(r"(?:[^&]|\b|^)#(\d+)\b", text): 41 | matches.append(match) 42 | 43 | for match in ReverseProxy(matches): 44 | start = match.start() 45 | end = match.end() 46 | number = match.groups()[0] 47 | url = reverse('helpdesk_view', args=[number]) 48 | try: 49 | ticket = Ticket.objects.get(id=number) 50 | except Ticket.DoesNotExist: 51 | ticket = None 52 | 53 | if ticket: 54 | style = ticket.get_status_display() 55 | text = "%s #%s%s" % (text[:match.start()], url, style, match.groups()[0], text[match.end():]) 56 | return mark_safe(text) 57 | 58 | register = template.Library() 59 | register.filter(num_to_link) 60 | -------------------------------------------------------------------------------- /helpdesk/views/kb.py: -------------------------------------------------------------------------------- 1 | """ 2 | django-helpdesk - A Django powered ticket tracker for small enterprise. 3 | 4 | (c) Copyright 2008 Jutda. All Rights Reserved. See LICENSE for details. 5 | 6 | views/kb.py - Public-facing knowledgebase views. The knowledgebase is a 7 | simple categorised question/answer system to show common 8 | resolutions to common problems. 9 | """ 10 | 11 | from datetime import datetime 12 | 13 | from django.http import HttpResponseRedirect 14 | from django.shortcuts import render_to_response, get_object_or_404 15 | from django.template import RequestContext 16 | from django.utils.translation import ugettext as _ 17 | 18 | from helpdesk import settings as helpdesk_settings 19 | from helpdesk.models import KBCategory, KBItem 20 | 21 | 22 | def index(request): 23 | category_list = KBCategory.objects.all() 24 | # TODO: It'd be great to have a list of most popular items here. 25 | return render_to_response('helpdesk/kb_index.html', 26 | RequestContext(request, { 27 | 'kb_categories': category_list, 28 | 'helpdesk_settings': helpdesk_settings, 29 | })) 30 | 31 | 32 | def category(request, slug): 33 | category = get_object_or_404(KBCategory, slug__iexact=slug) 34 | items = category.kbitem_set.all() 35 | return render_to_response('helpdesk/kb_category.html', 36 | RequestContext(request, { 37 | 'category': category, 38 | 'items': items, 39 | 'helpdesk_settings': helpdesk_settings, 40 | })) 41 | 42 | 43 | def item(request, item): 44 | item = get_object_or_404(KBItem, pk=item) 45 | return render_to_response('helpdesk/kb_item.html', 46 | RequestContext(request, { 47 | 'item': item, 48 | 'helpdesk_settings': helpdesk_settings, 49 | })) 50 | 51 | 52 | def vote(request, item): 53 | item = get_object_or_404(KBItem, pk=item) 54 | vote = request.GET.get('vote', None) 55 | if vote in ('up', 'down'): 56 | item.votes += 1 57 | if vote == 'up': 58 | item.recommendations += 1 59 | item.save() 60 | 61 | return HttpResponseRedirect(item.get_absolute_url()) 62 | 63 | -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery.jqplot/plugins/jqplot.trendline.min.js: -------------------------------------------------------------------------------- 1 | /* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com 2 | jsDate | (c) 2010-2013 Chris Leonello 3 | */(function(f){f.jqplot.Trendline=function(){this.show=f.jqplot.config.enablePlugins;this.color="#666666";this.renderer=new f.jqplot.LineRenderer();this.rendererOptions={marker:{show:false}};this.label="";this.type="linear";this.shadow=true;this.markerRenderer={show:false};this.lineWidth=1.5;this.shadowAngle=45;this.shadowOffset=1;this.shadowAlpha=0.07;this.shadowDepth=3;this.isTrendline=true};f.jqplot.postSeriesInitHooks.push(e);f.jqplot.postDrawSeriesHooks.push(g);f.jqplot.addLegendRowHooks.push(a);function a(k){var j=null;if(k.trendline&&k.trendline.show){var i=k.trendline.label.toString();if(i){j={label:i,color:k.trendline.color}}}return j}function e(m,k,j,i,l){if(this._type&&(this._type==="line"||this._type=="bar")){this.trendline=new f.jqplot.Trendline();i=i||{};f.extend(true,this.trendline,{color:this.color},j.trendline,i.trendline);this.trendline.renderer.init.call(this.trendline,null)}}function g(m,i){i=f.extend(true,{},this.trendline,i);if(this.trendline&&i.show){var k;var l=i.data||this.data;k=c(l,this.trendline.type);var j=i.gridData||this.renderer.makeGridData.call(this,k.data);this.trendline.renderer.draw.call(this.trendline,m,j,{showLine:true,shadow:this.trendline.shadow})}}function b(w,v,n){var u=(n==null)?"linear":n;var s=w.length;var t;var z;var o=0;var m=0;var r=0;var q=0;var l=0;var j=[];var k=[];if(u=="linear"){k=w;j=v}else{if(u=="exp"||u=="exponential"){for(var p=0;p{% trans "RSS Feeds" %} 5 | 6 |

{% 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." %}

7 | 8 |
9 |
{% trans "RSS Icon" %}{% trans "My Open Tickets" %}
10 |
{% trans "A summary of your open tickets - useful for getting alerted to new tickets opened for you" %}
11 | 12 |
{% trans "RSS Icon" %}{% trans "Latest Activity" %}
13 |
{% trans "A summary of all helpdesk activity - including comments, emails, attachments, and more" %}
14 | 15 |
{% trans "RSS Icon" %}{% trans "Unassigned Tickets" %}
16 |
{% trans "All unassigned tickets - useful for being alerted to new tickets opened by the public via the web or via e-mail" %}
17 |
18 | 19 |

{% trans "These RSS feeds allow you to view a summary of either your own tickets, or all tickets, for each of the queues in your helpdesk. For example, if you manage the staff who utilise a particular queue, this may be used to view new tickets coming into that queue." %}

20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% for queue in queues %} 28 | 29 | 30 | 31 | 32 | {% endfor %} 33 | 34 |
{% trans "Per-Queue Feeds" %}
{% trans "Queue" %}{% trans "All Open Tickets" %}{% trans "My Open Tickets" %}
{{ queue.title }}{% trans "RSS Icon" %}{% trans "RSS Icon" %}
35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 2009-09-09 r138 Issue #104 Add a CHANGELOG file 2 | 3 | 2009-09-09 r139 Issue #102 Add Ticket CC's 4 | Add rudimentary CC: functionality on tickets, controlled by staff users. CC's 5 | can be e-mail addresses or users, who will receive copies of all emails sent 6 | to the Submitter. This is a work in progress. 7 | 8 | 2009-09-09 r140 Issue #13 Add Tags to tickets 9 | Patch courtesy of david@zettazebra.com, adds the ability to add tags to 10 | tickets if django-tagging is installed and in use. If django-tagging isn't 11 | being used, no change is visible to the user. 12 | 13 | 2009-10-13 r141 Issue #118 Incorrect locale handling on email templates 14 | Patch courtesy of hgeerts. Corrects the handling of locale on email 15 | templaets, defaulting to English if no locale is provided. 16 | 17 | 2009-10-13 r142 Issue #117 Incorrect I18N usage in a few spots 18 | Patch thanks to hgeerts. 19 | 20 | 2009-10-13 r143 Issue #113 - Clicking Queue names on the Dashboard was showing 21 | all tickets; now only shows open tickets. Thanks to Andreas Kotowicz for the 22 | sugestion. 23 | 24 | 2009-12-16 r144 Issue #122 - Infinite loop when most recent ticket was 25 | opened in December. Thanks to Chris Vigelius for the report. 26 | 27 | 2009-12-16 r145 issue #123 - Google Chart doesn't show when there is a large 28 | volume of data in the system. This patch restricts the chart to 1000px wide. 29 | 30 | 2009-12-16 r146 Issue #121 Formatting fix for email subjects. Thanks, 31 | Andreas Kotowicz. 32 | 33 | 2009-12-16 r147 Issue #119 Update Russian translation, thanks to Alex Yakovlev 34 | 35 | 2009-12-23 r148 Issue #125 Errors occurring when running reports with no data 36 | 37 | 2010-01-20 r149 Issue #126 Reports didn't work with transalations. 38 | 39 | 2010-01-20 r150 Issue #127 Add german transalation, courtesy of openinformation.org 40 | 41 | 2009-01-20 r151 Issue #128 If queue name has a dash in it, email imported failed. Thanks to enix.org for the patch. 42 | 43 | 2010-01-21 r152 Fix indentation error caused by issue #126. 44 | 45 | 2010-01-26 r153 Fix issue #129 - can not 'unassign' tickets. Thanks to 46 | lukeman for the patch. 47 | 48 | 2010-01-26 r154 Fix bug in the code from Issue #102 where TicketCC's couldn't 49 | be deleted. Thanks again to lukeman. 50 | 51 | 2010-01-31 r155 Fix bug caused by issue #129 - ticket followup titles being 52 | set incorrectly. Thanks to Lukeman for the fix. 53 | 54 | 2010-07-16 r157 Fix issues #141, #142 - IMAP infinite loops and ticket 55 | pagination issues. Thanks to Walter Doekes for the patches. 56 | 57 | 2010-07-16 r158 New CSRF functionality for Django 1.1+. Thanks to 58 | 'litchfield4' for the patch. 59 | 60 | 2010-09-04 r159 Error when updating multiple tickets. Issue #135. 61 | 62 | 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. 63 | 64 | 2010-09-04 r161 Fix jQuery filename in public templates. Thanks to bruno.braga for the fix. 65 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | django-helpdesk is an open-source project and as such contributions from the community are welcomed and encouraged. 5 | 6 | Licensing 7 | --------- 8 | 9 | All contributions to django-helpdesk must be under the BSD license documented in our :doc:`license` page. All code submitted (in any way: via e-mail, via GitHub forks, attachments, etc) are assumed to be open-source and licensed under the BSD license. 10 | 11 | If you or your organisation does not accept these license terms then we cannot accept your contribution. Please reconsider! 12 | 13 | Translations 14 | ------------ 15 | 16 | .. image:: http://www.transifex.net/projects/p/django-helpdesk/resource/core/chart/image_png 17 | 18 | Although django-helpdesk has originally been written for the English language, there are already multiple translations to Spanish, Polish, and German and more translations are welcomed. 19 | 20 | Translations are handled using the excellent Transifex service which is much easier for most users than manually editing .po files. It also allows collaborative translation. If you want to help translate django-helpdesk into languages other than English, we encourage you to make use of our Transifex project: 21 | 22 | http://www.transifex.net/projects/p/django-helpdesk/resource/core/ 23 | 24 | Once you have translated content via Transifex, please raise an issue on the project Github page to let us know it's ready to import. 25 | 26 | Code changes 27 | ------------ 28 | 29 | Please fork the project on GitHub, make your changes, and log a pull request to get the changes pulled back into my repository. 30 | 31 | Wherever possible please break git commits up into small chunks that are specific to a single bit of functionality. For example, a commit should not contain both new functionality *and* a bugfix; the new function and the bugfix should be separate commits wherever possible. 32 | 33 | Commit messages should also explain *what*, precisely, has been changed. 34 | 35 | If you have any questions, please contact the project co-ordinator, Ross Poulton, at ross@rossp.org. 36 | 37 | Tests 38 | ----- 39 | 40 | Currently, test coverage is very low. We're working on increasing this, and to make life easier we are using `Travis CI`_ for continuous integration. This means that the test suite is run every time a code change is made, so we can try and make sure we avoid basic bugs and other regressions. 41 | 42 | Please include tests in the ``tests/`` folder when committing code changes. 43 | 44 | .. _Travis CI: http://travis-ci.org/ 45 | 46 | Database schema changes 47 | ----------------------- 48 | 49 | As well as making your normal code changes to ``models.py``, please generate a South migration file and commit it with your code. You will want to use a command similar to the following:: 50 | 51 | ./manage.py migrate helpdesk --auto [migration_name] 52 | 53 | Make sure that ``migration_name`` is a sensible single-string explanation of what this migration does, such as *add_priority_options* or *add_basket_table* 54 | 55 | This will add a file to the ``migrations/`` folder, which must be committed to git with your other code changes. 56 | -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery.jqplot/plugins/jqplot.canvasAxisTickRenderer.min.js: -------------------------------------------------------------------------------- 1 | /* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com 2 | jsDate | (c) 2010-2013 Chris Leonello 3 | */(function(a){a.jqplot.CanvasAxisTickRenderer=function(b){this.mark="outside";this.showMark=true;this.showGridline=true;this.isMinorTick=false;this.angle=0;this.markSize=4;this.show=true;this.showLabel=true;this.labelPosition="auto";this.label="";this.value=null;this._styles={};this.formatter=a.jqplot.DefaultTickFormatter;this.formatString="";this.prefix="";this.fontFamily='"Trebuchet MS", Arial, Helvetica, sans-serif';this.fontSize="10pt";this.fontWeight="normal";this.fontStretch=1;this.textColor="#666666";this.enableFontSupport=true;this.pt2px=null;this._elem;this._ctx;this._plotWidth;this._plotHeight;this._plotDimensions={height:null,width:null};a.extend(true,this,b);var c={fontSize:this.fontSize,fontWeight:this.fontWeight,fontStretch:this.fontStretch,fillStyle:this.textColor,angle:this.getAngleRad(),fontFamily:this.fontFamily};if(this.pt2px){c.pt2px=this.pt2px}if(this.enableFontSupport){if(a.jqplot.support_canvas_text()){this._textRenderer=new a.jqplot.CanvasFontRenderer(c)}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(c)}}else{this._textRenderer=new a.jqplot.CanvasTextRenderer(c)}};a.jqplot.CanvasAxisTickRenderer.prototype.init=function(b){a.extend(true,this,b);this._textRenderer.init({fontSize:this.fontSize,fontWeight:this.fontWeight,fontStretch:this.fontStretch,fillStyle:this.textColor,angle:this.getAngleRad(),fontFamily:this.fontFamily})};a.jqplot.CanvasAxisTickRenderer.prototype.getWidth=function(d){if(this._elem){return this._elem.outerWidth(true)}else{var f=this._textRenderer;var c=f.getWidth(d);var e=f.getHeight(d);var b=Math.abs(Math.sin(f.angle)*e)+Math.abs(Math.cos(f.angle)*c);return b}};a.jqplot.CanvasAxisTickRenderer.prototype.getHeight=function(d){if(this._elem){return this._elem.outerHeight(true)}else{var f=this._textRenderer;var c=f.getWidth(d);var e=f.getHeight(d);var b=Math.abs(Math.cos(f.angle)*e)+Math.abs(Math.sin(f.angle)*c);return b}};a.jqplot.CanvasAxisTickRenderer.prototype.getTop=function(b){if(this._elem){return this._elem.position().top}else{return null}};a.jqplot.CanvasAxisTickRenderer.prototype.getAngleRad=function(){var b=this.angle*Math.PI/180;return b};a.jqplot.CanvasAxisTickRenderer.prototype.setTick=function(b,d,c){this.value=b;if(c){this.isMinorTick=true}return this};a.jqplot.CanvasAxisTickRenderer.prototype.draw=function(c,f){if(!this.label){this.label=this.prefix+this.formatter(this.formatString,this.value)}if(this._elem){if(a.jqplot.use_excanvas&&window.G_vmlCanvasManager.uninitElement!==undefined){window.G_vmlCanvasManager.uninitElement(this._elem.get(0))}this._elem.emptyForce();this._elem=null}var e=f.canvasManager.getCanvas();this._textRenderer.setText(this.label,c);var b=this.getWidth(c);var d=this.getHeight(c);e.width=b;e.height=d;e.style.width=b;e.style.height=d;e.style.textAlign="left";e.style.position="absolute";e=f.canvasManager.initCanvas(e);this._elem=a(e);this._elem.css(this._styles);this._elem.addClass("jqplot-"+this.axis+"-tick");e=null;return this._elem};a.jqplot.CanvasAxisTickRenderer.prototype.pack=function(){this._textRenderer.draw(this._elem.get(0).getContext("2d"),this.label)}})(jQuery); -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/public_view_ticket.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/public_base.html" %}{% load i18n humanize %} 2 | {% block helpdesk_title %}{% trans "View a Ticket" %}{% endblock %} 3 | 4 | {% block helpdesk_body %} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% for customfield in ticket.ticketcustomfieldvalue_set.all %} 28 | 29 | 30 | 31 | {% endfor %} 32 | 33 | {% if tags_enabled %} 34 | 35 | 36 | 37 | 38 | {% endif %} 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | {% if ticket.resolution %} 48 | 49 | 50 | 51 | 52 | {% endif %} 53 | 54 |
{{ ticket.ticket }} . {{ ticket.title }} [{{ ticket.get_status }}]
{% blocktrans with ticket.queue as queue_name %}Queue: {{ queue_name }}{% endblocktrans %}
{% trans "Submitted On" %}{{ ticket.created|date:"r" }} ({{ ticket.created|naturaltime }})
{% trans "Submitter E-Mail" %}{{ ticket.submitter_email }}
{% trans "Priority" %}{{ ticket.get_priority_display }}
{{ customfield.field.label }}{{ customfield.value }}
{% trans "Tags" %}{{ ticket.tags }}
{% trans "Description" %}
{{ ticket.description|force_escape|urlizetrunc:50|linebreaksbr }}
{% trans "Resolution" %}{% ifequal ticket.get_status_display "Resolved" %} {% trans "Accept" %}{% endifequal %}
{{ ticket.resolution|urlizetrunc:50|linebreaksbr }}
55 | 56 | {% if ticket.followup_set.public_followups %} 57 |

{% trans "Follow-Ups" %}

58 | {% load ticket_to_link %} 59 | {% for followup in ticket.followup_set.public_followups %} 60 |
61 |
{{ followup.title }}
62 | {{ followup.comment|force_escape|urlizetrunc:50|num_to_link|linebreaksbr }} 63 | {% if followup.ticketchange_set.all %}
    64 | {% for change in followup.ticketchange_set.all %} 65 |
  • {% blocktrans with change.field as field and change.old_value as old_value and change.new_value as new_value %}Changed {{ field }} from {{ old_value }} to {{ new_value }}.{% endblocktrans %}
  • 66 | {% endfor %} 67 |
{% endif %} 68 | {% for attachment in followup.attachment_set.all %}{% if forloop.first %}
    {% endif %} 69 |
  • {{ attachment.filename }} ({{ attachment.mime_type }}, {{ attachment.size|filesizeformat }})
  • 70 | {% if forloop.last %}
{% endif %} 71 | {% endfor %} 72 |
73 | {% endfor %} 74 | {% endif %} 75 | 76 | {% endblock %} 77 | -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery.jqplot/plugins/jqplot.blockRenderer.min.js: -------------------------------------------------------------------------------- 1 | /* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com 2 | jsDate | (c) 2010-2013 Chris Leonello 3 | */(function(a){a.jqplot.BlockRenderer=function(){a.jqplot.LineRenderer.call(this)};a.jqplot.BlockRenderer.prototype=new a.jqplot.LineRenderer();a.jqplot.BlockRenderer.prototype.constructor=a.jqplot.BlockRenderer;a.jqplot.BlockRenderer.prototype.init=function(b){this.css={padding:"2px",border:"1px solid #999",textAlign:"center"};this.escapeHtml=false;this.insertBreaks=true;this.varyBlockColors=false;a.extend(true,this,b);if(this.css.backgroundColor){this.color=this.css.backgroundColor}else{if(this.css.background){this.color=this.css.background}else{if(!this.varyBlockColors){this.css.background=this.color}}}this.canvas=new a.jqplot.BlockCanvas();this.shadowCanvas=new a.jqplot.BlockCanvas();this.canvas._plotDimensions=this._plotDimensions;this.shadowCanvas._plotDimensions=this._plotDimensions;this._type="block";this.moveBlock=function(l,j,i,e){var c=this.canvas._elem.children(":eq("+l+")");this.data[l][0]=j;this.data[l][1]=i;this._plotData[l][0]=j;this._plotData[l][1]=i;this._stackData[l][0]=j;this._stackData[l][1]=i;this.gridData[l][0]=this._xaxis.series_u2p(j);this.gridData[l][1]=this._yaxis.series_u2p(i);var k=c.outerWidth();var f=c.outerHeight();var d=this.gridData[l][0]-k/2+"px";var g=this.gridData[l][1]-f/2+"px";if(e){if(parseInt(e,10)){e=parseInt(e,10)}c.animate({left:d,top:g},e)}else{c.css({left:d,top:g})}c=null}};a.jqplot.BlockRenderer.prototype.draw=function(q,o,r){if(this.plugins.pointLabels){this.plugins.pointLabels.show=false}var f,c,l,o,p,k,n,g,e,m;var b=(r!=undefined)?r:{};var j=new a.jqplot.ColorGenerator(this.seriesColors);this.canvas._elem.empty();for(f=0;f")}k=a.extend(true,{},this.css,k);c=a('
');this.canvas._elem.append(c);this.escapeHtml?c.text(p):c.html(p);delete k.position;delete k.marginRight;delete k.marginLeft;if(!k.background&&!k.backgroundColor&&!k.backgroundImage){k.background=j.next()}c.css(k);n=c.outerWidth();g=c.outerHeight();e=o[0]-n/2+"px";m=o[1]-g/2+"px";c.css({left:e,top:m});c=null}};a.jqplot.BlockCanvas=function(){a.jqplot.ElemContainer.call(this);this._ctx};a.jqplot.BlockCanvas.prototype=new a.jqplot.ElemContainer();a.jqplot.BlockCanvas.prototype.constructor=a.jqplot.BlockCanvas;a.jqplot.BlockCanvas.prototype.createElement=function(i,e,c){this._offsets=i;var b="jqplot-blockCanvas";if(e!=undefined){b=e}var g;if(this._elem){g=this._elem.get(0)}else{g=document.createElement("div")}if(c!=undefined){this._plotDimensions=c}var d=this._plotDimensions.width-this._offsets.left-this._offsets.right+"px";var f=this._plotDimensions.height-this._offsets.top-this._offsets.bottom+"px";this._elem=a(g);this._elem.css({position:"absolute",width:d,height:f,left:this._offsets.left,top:this._offsets.top});this._elem.addClass(b);return this._elem};a.jqplot.BlockCanvas.prototype.setContext=function(){this._ctx={canvas:{width:0,height:0},clearRect:function(){return null}};return this._ctx}})(jQuery); -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery.jqplot/jquery.jqplot.min.css: -------------------------------------------------------------------------------- 1 | .jqplot-target{position:relative;color:#666;font-family:"Trebuchet MS",Arial,Helvetica,sans-serif;font-size:1em}.jqplot-axis{font-size:.75em}.jqplot-xaxis{margin-top:10px}.jqplot-x2axis{margin-bottom:10px}.jqplot-yaxis{margin-right:10px}.jqplot-y2axis,.jqplot-y3axis,.jqplot-y4axis,.jqplot-y5axis,.jqplot-y6axis,.jqplot-y7axis,.jqplot-y8axis,.jqplot-y9axis,.jqplot-yMidAxis{margin-left:10px;margin-right:10px}.jqplot-axis-tick,.jqplot-xaxis-tick,.jqplot-yaxis-tick,.jqplot-x2axis-tick,.jqplot-y2axis-tick,.jqplot-y3axis-tick,.jqplot-y4axis-tick,.jqplot-y5axis-tick,.jqplot-y6axis-tick,.jqplot-y7axis-tick,.jqplot-y8axis-tick,.jqplot-y9axis-tick,.jqplot-yMidAxis-tick{position:absolute;white-space:pre}.jqplot-xaxis-tick{top:0;left:15px;vertical-align:top}.jqplot-x2axis-tick{bottom:0;left:15px;vertical-align:bottom}.jqplot-yaxis-tick{right:0;top:15px;text-align:right}.jqplot-yaxis-tick.jqplot-breakTick{right:-20px;margin-right:0;padding:1px 5px 1px 5px;z-index:2;font-size:1.5em}.jqplot-y2axis-tick,.jqplot-y3axis-tick,.jqplot-y4axis-tick,.jqplot-y5axis-tick,.jqplot-y6axis-tick,.jqplot-y7axis-tick,.jqplot-y8axis-tick,.jqplot-y9axis-tick{left:0;top:15px;text-align:left}.jqplot-yMidAxis-tick{text-align:center;white-space:nowrap}.jqplot-xaxis-label{margin-top:10px;font-size:11pt;position:absolute}.jqplot-x2axis-label{margin-bottom:10px;font-size:11pt;position:absolute}.jqplot-yaxis-label{margin-right:10px;font-size:11pt;position:absolute}.jqplot-yMidAxis-label{font-size:11pt;position:absolute}.jqplot-y2axis-label,.jqplot-y3axis-label,.jqplot-y4axis-label,.jqplot-y5axis-label,.jqplot-y6axis-label,.jqplot-y7axis-label,.jqplot-y8axis-label,.jqplot-y9axis-label{font-size:11pt;margin-left:10px;position:absolute}.jqplot-meterGauge-tick{font-size:.75em;color:#999}.jqplot-meterGauge-label{font-size:1em;color:#999}table.jqplot-table-legend{margin-top:12px;margin-bottom:12px;margin-left:12px;margin-right:12px}table.jqplot-table-legend,table.jqplot-cursor-legend{background-color:rgba(255,255,255,0.6);border:1px solid #ccc;position:absolute;font-size:.75em}td.jqplot-table-legend{vertical-align:middle}td.jqplot-seriesToggle:hover,td.jqplot-seriesToggle:active{cursor:pointer}.jqplot-table-legend .jqplot-series-hidden{text-decoration:line-through}div.jqplot-table-legend-swatch-outline{border:1px solid #ccc;padding:1px}div.jqplot-table-legend-swatch{width:0;height:0;border-top-width:5px;border-bottom-width:5px;border-left-width:6px;border-right-width:6px;border-top-style:solid;border-bottom-style:solid;border-left-style:solid;border-right-style:solid}.jqplot-title{top:0;left:0;padding-bottom:.5em;font-size:1.2em}table.jqplot-cursor-tooltip{border:1px solid #ccc;font-size:.75em}.jqplot-cursor-tooltip{border:1px solid #ccc;font-size:.75em;white-space:nowrap;background:rgba(208,208,208,0.5);padding:1px}.jqplot-highlighter-tooltip,.jqplot-canvasOverlay-tooltip{border:1px solid #ccc;font-size:.75em;white-space:nowrap;background:rgba(208,208,208,0.5);padding:1px}.jqplot-point-label{font-size:.75em;z-index:2}td.jqplot-cursor-legend-swatch{vertical-align:middle;text-align:center}div.jqplot-cursor-legend-swatch{width:1.2em;height:.7em}.jqplot-error{text-align:center}.jqplot-error-message{position:relative;top:46%;display:inline-block}div.jqplot-bubble-label{font-size:.8em;padding-left:2px;padding-right:2px;color:rgb(20%,20%,20%)}div.jqplot-bubble-label.jqplot-bubble-label-highlight{background:rgba(90%,90%,90%,0.7)}div.jqplot-noData-container{text-align:center;background-color:rgba(96%,96%,96%,0.3)} -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery.jqplot/plugins/jqplot.json2.min.js: -------------------------------------------------------------------------------- 1 | /* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com 2 | jsDate | (c) 2010-2013 Chris Leonello 3 | */(function($){$.jqplot.JSON=window.JSON;if(!window.JSON){$.jqplot.JSON={}}function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i{% trans "Knowledgebase Articles" %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for category in kb_categories %} 15 | 16 | 17 | {% endfor %} 18 | 19 |
{% trans "Knowledgebase Categories" %}
{% trans "Category" %}
{{ category.title }}
{{ category.description }}
20 | {% endif %} 21 | 22 | {% if helpdesk_settings.HELPDESK_SUBMIT_A_TICKET_PUBLIC %} 23 |
24 |
25 | 26 |
27 |

{% trans "Submit a Ticket" %}

28 |

{% trans "All fields are required." %} {% trans "Please provide as descriptive a title and description as possible." %}

29 | 30 |
31 |
32 | {{ form|bootstrap }} 33 | {% comment %} 34 | {% for field in form %} 35 | 36 | {% if field.is_hidden %} 37 | {{ field }} 38 | {% else %} 39 | 40 | 41 |
42 | {% if not field.field.required %} {% trans "(Optional)" %}{% endif %} 43 |
{{ field }}
44 | {% if field.errors %}
{{ field.errors }}
{% endif %} 45 | {% if field.help_text %}{{ field.help_text }}{% endif %} 46 |
47 | 48 | {% endif %} 49 | 50 | {% endfor %} 51 | {% endcomment %} 52 | 53 |
54 | 55 |
56 |
57 | 58 | {% csrf_token %}
59 |
60 |
61 |
62 | {% endif %} 63 | 64 | {% if not helpdesk_settings.HELPDESK_VIEW_A_TICKET_PUBLIC and not helpdesk_settings.HELPDESK_SUBMIT_A_TICKET_PUBLIC %} 65 |

{% trans "Please use button at upper right to login first." %}

66 | {% endif %} 67 | 68 | {% if helpdesk_settings.HELPDESK_VIEW_A_TICKET_PUBLIC %} 69 |
70 |
71 |
72 |

{% trans "View a Ticket" %}

73 | 74 |
75 |
76 |
77 | 78 |
79 |
80 |
81 | 82 |
83 |
84 |
85 | 86 |
87 |
88 | {% csrf_token %}
89 |
90 | {% endif %} 91 | 92 | {% endblock %} 93 | -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery.jqplot/plugins/jqplot.ohlcRenderer.min.js: -------------------------------------------------------------------------------- 1 | /* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com 2 | jsDate | (c) 2010-2013 Chris Leonello 3 | */(function(a){a.jqplot.OHLCRenderer=function(){a.jqplot.LineRenderer.call(this);this.candleStick=false;this.tickLength="auto";this.bodyWidth="auto";this.openColor=null;this.closeColor=null;this.wickColor=null;this.fillUpBody=false;this.fillDownBody=true;this.upBodyColor=null;this.downBodyColor=null;this.hlc=false;this.lineWidth=1.5;this._tickLength;this._bodyWidth};a.jqplot.OHLCRenderer.prototype=new a.jqplot.LineRenderer();a.jqplot.OHLCRenderer.prototype.constructor=a.jqplot.OHLCRenderer;a.jqplot.OHLCRenderer.prototype.init=function(e){e=e||{};this.lineWidth=e.lineWidth||1.5;a.jqplot.LineRenderer.prototype.init.call(this,e);this._type="ohlc";var b=this._yaxis._dataBounds;var f=this._plotData;if(f[0].length<5){this.renderer.hlc=true;for(var c=0;cb.max||b.max==null){b.max=f[c][1]}}}else{for(var c=0;cb.max||b.max==null){b.max=f[c][2]}}}};a.jqplot.OHLCRenderer.prototype.draw=function(A,N,j){var J=this.data;var v=this._xaxis.min;var z=this._xaxis.max;var l=0;var K=J.length;var p=this._xaxis.series_u2p;var G=this._yaxis.series_u2p;var D,E,f,M,F,n,O,C;var y;var u=this.renderer;var s=(j!=undefined)?j:{};var k=(s.shadow!=undefined)?s.shadow:this.shadow;var B=(s.fill!=undefined)?s.fill:this.fill;var c=(s.fillAndStroke!=undefined)?s.fillAndStroke:this.fillAndStroke;u.bodyWidth=(s.bodyWidth!=undefined)?s.bodyWidth:u.bodyWidth;u.tickLength=(s.tickLength!=undefined)?s.tickLength:u.tickLength;A.save();if(this.show){var m,q,g,Q,t;for(var D=0;Dq){if(u.wickColor){y.color=u.wickColor}else{if(u.downBodyColor){y.color=u.downBodyColor}}f=a.extend(true,{},s,y);u.shapeRenderer.draw(A,[[m,g],[m,q]],f);u.shapeRenderer.draw(A,[[m,t],[m,Q]],f);y={};M=q;F=t-q;if(u.fillDownBody){y.fillRect=true}else{y.strokeRect=true;n=n-this.lineWidth;O=m-n/2}if(u.downBodyColor){y.color=u.downBodyColor;y.fillStyle=u.downBodyColor}C=[O,M,n,F]}else{if(u.wickColor){y.color=u.wickColor}f=a.extend(true,{},s,y);u.shapeRenderer.draw(A,[[m,g],[m,Q]],f);y={};y.fillRect=false;y.strokeRect=false;O=[m-n/2,q];M=[m+n/2,t];n=null;F=null;C=[O,M]}}f=a.extend(true,{},s,y);u.shapeRenderer.draw(A,C,f)}else{E=s.color;if(u.openColor){s.color=u.openColor}if(!u.hlc){u.shapeRenderer.draw(A,[[m-u._tickLength,q],[m,q]],s)}s.color=E;if(u.wickColor){s.color=u.wickColor}u.shapeRenderer.draw(A,[[m,g],[m,Q]],s);s.color=E;if(u.closeColor){s.color=u.closeColor}u.shapeRenderer.draw(A,[[m,t],[m+u._tickLength,t]],s);s.color=E}}}A.restore()};a.jqplot.OHLCRenderer.prototype.drawShadow=function(b,d,c){};a.jqplot.OHLCRenderer.checkOptions=function(d,c,b){if(!b.highlighter){b.highlighter={showMarker:false,tooltipAxes:"y",yvalues:4,formatString:'
date:%s
open:%s
hi:%s
low:%s
close:%s
'}}}})(jQuery); -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery.jqplot/plugins/jqplot.dragable.min.js: -------------------------------------------------------------------------------- 1 | /* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com 2 | jsDate | (c) 2010-2013 Chris Leonello 3 | */(function(d){d.jqplot.Dragable=function(g){this.markerRenderer=new d.jqplot.MarkerRenderer({shadow:false});this.shapeRenderer=new d.jqplot.ShapeRenderer();this.isDragging=false;this.isOver=false;this._ctx;this._elem;this._point;this._gridData;this.color;this.constrainTo="none";d.extend(true,this,g)};function b(){d.jqplot.GenericCanvas.call(this);this.isDragging=false;this.isOver=false;this._neighbor;this._cursors=[]}b.prototype=new d.jqplot.GenericCanvas();b.prototype.constructor=b;d.jqplot.Dragable.parseOptions=function(i,h){var g=h||{};this.plugins.dragable=new d.jqplot.Dragable(g.dragable);this.isDragable=d.jqplot.config.enablePlugins};d.jqplot.Dragable.postPlotDraw=function(){if(this.plugins.dragable&&this.plugins.dragable.highlightCanvas){this.plugins.dragable.highlightCanvas.resetCanvas();this.plugins.dragable.highlightCanvas=null}this.plugins.dragable={previousCursor:"auto",isOver:false};this.plugins.dragable.dragCanvas=new b();this.eventCanvas._elem.before(this.plugins.dragable.dragCanvas.createElement(this._gridPadding,"jqplot-dragable-canvas",this._plotDimensions,this));var g=this.plugins.dragable.dragCanvas.setContext()};d.jqplot.preParseSeriesOptionsHooks.push(d.jqplot.Dragable.parseOptions);d.jqplot.postDrawHooks.push(d.jqplot.Dragable.postPlotDraw);d.jqplot.eventListenerHooks.push(["jqplotMouseMove",e]);d.jqplot.eventListenerHooks.push(["jqplotMouseDown",c]);d.jqplot.eventListenerHooks.push(["jqplotMouseUp",a]);function f(n,p){var q=n.series[p.seriesIndex];var m=q.plugins.dragable;var h=q.markerRenderer;var i=m.markerRenderer;i.style=h.style;i.lineWidth=h.lineWidth+2.5;i.size=h.size+5;if(!m.color){var l=d.jqplot.getColorComponents(h.color);var o=[l[0],l[1],l[2]];var k=(l[3]>=0.6)?l[3]*0.6:l[3]*(2-l[3]);m.color="rgba("+o[0]+","+o[1]+","+o[2]+","+k+")"}i.color=m.color;i.init();var g=(p.pointIndex>0)?p.pointIndex-1:0;var j=p.pointIndex+2;m._gridData=q.gridData.slice(g,j)}function e(o,l,h,t,m){if(m.plugins.dragable.dragCanvas.isDragging){var u=m.plugins.dragable.dragCanvas;var i=u._neighbor;var w=m.series[i.seriesIndex];var k=w.plugins.dragable;var r=w.gridData;var p=(k.constrainTo=="y")?i.gridData[0]:l.x;var n=(k.constrainTo=="x")?i.gridData[1]:l.y;var g=w._xaxis.series_p2u(p);var q=w._yaxis.series_p2u(n);var v=u._ctx;v.clearRect(0,0,v.canvas.width,v.canvas.height);if(i.pointIndex>0){k._gridData[1]=[p,n]}else{k._gridData[0]=[p,n]}m.series[i.seriesIndex].draw(u._ctx,{gridData:k._gridData,shadow:false,preventJqPlotSeriesDrawTrigger:true,color:k.color,markerOptions:{color:k.color,shadow:false},trendline:{show:false}});m.target.trigger("jqplotSeriesPointChange",[i.seriesIndex,i.pointIndex,[g,q],[p,n]])}else{if(t!=null){var j=m.series[t.seriesIndex];if(j.isDragable){var u=m.plugins.dragable.dragCanvas;if(!u.isOver){u._cursors.push(o.target.style.cursor);o.target.style.cursor="pointer"}u.isOver=true}}else{if(t==null){var u=m.plugins.dragable.dragCanvas;if(u.isOver){o.target.style.cursor=u._cursors.pop();u.isOver=false}}}}}function c(k,i,g,l,j){var m=j.plugins.dragable.dragCanvas;m._cursors.push(k.target.style.cursor);if(l!=null){var o=j.series[l.seriesIndex];var h=o.plugins.dragable;if(o.isDragable&&!m.isDragging){m._neighbor=l;m.isDragging=true;f(j,l);h.markerRenderer.draw(o.gridData[l.pointIndex][0],o.gridData[l.pointIndex][1],m._ctx);k.target.style.cursor="move";j.target.trigger("jqplotDragStart",[l.seriesIndex,l.pointIndex,i,g])}}else{var n=m._ctx;n.clearRect(0,0,n.canvas.width,n.canvas.height);m.isDragging=false}}function a(m,j,g,o,k){if(k.plugins.dragable.dragCanvas.isDragging){var p=k.plugins.dragable.dragCanvas;var q=p._ctx;q.clearRect(0,0,q.canvas.width,q.canvas.height);p.isDragging=false;var h=p._neighbor;var r=k.series[h.seriesIndex];var i=r.plugins.dragable;var n=(i.constrainTo=="y")?h.data[0]:g[r.xaxis];var l=(i.constrainTo=="x")?h.data[1]:g[r.yaxis];r.data[h.pointIndex][0]=n;r.data[h.pointIndex][1]=l;k.drawSeries({preventJqPlotSeriesDrawTrigger:true},h.seriesIndex);p._neighbor=null;m.target.style.cursor=p._cursors.pop();k.target.trigger("jqplotDragStop",[j,g])}}})(jQuery); -------------------------------------------------------------------------------- /quicktest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import argparse 4 | 5 | import django 6 | from django.conf import settings 7 | 8 | 9 | class QuickDjangoTest(object): 10 | """ 11 | A quick way to run the Django test suite without a fully-configured project. 12 | 13 | Example usage: 14 | 15 | >>> QuickDjangoTest('app1', 'app2') 16 | 17 | Based on a script published by Lukasz Dziedzia at: 18 | http://stackoverflow.com/questions/3841725/how-to-launch-tests-for-django-reusable-app 19 | """ 20 | DIRNAME = os.path.dirname(__file__) 21 | INSTALLED_APPS = ( 22 | 'django.contrib.auth', 23 | 'django.contrib.contenttypes', 24 | 'django.contrib.sessions', 25 | 'django.contrib.admin', 26 | 'django.contrib.staticfiles', 27 | 'django.contrib.messages', 28 | 'django.contrib.humanize', 29 | 'bootstrapform', 30 | ) 31 | MIDDLEWARE_CLASSES = [ 32 | 'django.contrib.sessions.middleware.SessionMiddleware', 33 | 'django.middleware.common.CommonMiddleware', 34 | 'django.middleware.csrf.CsrfViewMiddleware', 35 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 36 | 'django.contrib.messages.middleware.MessageMiddleware', 37 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 38 | ] 39 | 40 | def __init__(self, *args, **kwargs): 41 | self.apps = args 42 | # Get the version of the test suite 43 | self.version = self.get_test_version() 44 | # Call the appropriate one 45 | if self.version == 'new': 46 | self._new_tests() 47 | else: 48 | self._old_tests() 49 | 50 | def get_test_version(self): 51 | """ 52 | Figure out which version of Django's test suite we have to play with. 53 | """ 54 | if django.VERSION >= (1, 2): 55 | return 'new' 56 | else: 57 | return 'old' 58 | 59 | def _old_tests(self): 60 | """ 61 | Fire up the Django test suite from before version 1.2 62 | """ 63 | settings.configure(DEBUG = True, 64 | DATABASE_ENGINE = 'sqlite3', 65 | DATABASE_NAME = os.path.join(self.DIRNAME, 'database.db'), 66 | INSTALLED_APPS = self.INSTALLED_APPS + self.apps 67 | ) 68 | from django.test.simple import run_tests 69 | failures = run_tests(self.apps, verbosity=1) 70 | if failures: 71 | sys.exit(failures) 72 | 73 | def _new_tests(self): 74 | """ 75 | Fire up the Django test suite developed for version 1.2 76 | """ 77 | 78 | settings.configure( 79 | DEBUG = True, 80 | DATABASES = { 81 | 'default': { 82 | 'ENGINE': 'django.db.backends.sqlite3', 83 | 'NAME': os.path.join(self.DIRNAME, 'database.db'), 84 | 'USER': '', 85 | 'PASSWORD': '', 86 | 'HOST': '', 87 | 'PORT': '', 88 | } 89 | }, 90 | INSTALLED_APPS = self.INSTALLED_APPS + self.apps, 91 | MIDDLEWARE_CLASSES = self.MIDDLEWARE_CLASSES, 92 | ROOT_URLCONF = self.apps[0] + '.urls', 93 | ) 94 | 95 | if django.VERSION >= (1, 7): 96 | django.setup() 97 | 98 | from django.test.simple import DjangoTestSuiteRunner 99 | failures = DjangoTestSuiteRunner().run_tests(self.apps, verbosity=1) 100 | if failures: 101 | sys.exit(failures) 102 | 103 | if __name__ == '__main__': 104 | """ 105 | What do when the user hits this file from the shell. 106 | 107 | Example usage: 108 | 109 | $ python quicktest.py app1 app2 110 | 111 | """ 112 | parser = argparse.ArgumentParser( 113 | usage="[args]", 114 | description="Run Django tests on the provided applications." 115 | ) 116 | parser.add_argument('apps', nargs='+', type=str) 117 | args = parser.parse_args() 118 | QuickDjangoTest(*args.apps) 119 | 120 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/base.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load url from future %} 3 | {% load saved_queries %} 4 | {% load load_helpdesk_settings %} 5 | {% with request|load_helpdesk_settings as helpdesk_settings %} 6 | {% with user|saved_queries as user_saved_queries_ %} 7 | 8 | 9 | 10 | {% block helpdesk_title %}Helpdesk{% endblock %} :: {% trans "Powered by django-helpdesk" %} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 32 | 46 | 47 | {% block helpdesk_head %}{% endblock %} 48 | 49 | 50 |
51 | 55 | 56 |
57 | {% block helpdesk_body %}{% endblock %} 58 |
59 | 60 | 66 |
67 | {% include "helpdesk/debug.html" %} 68 | 69 | 70 | {% endwith %} 71 | {% endwith %} 72 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/ticket_desc_table.html: -------------------------------------------------------------------------------- 1 | {% load i18n humanize %}{% load url from future %} 2 | 3 | 4 | 9 | 10 | 11 | 12 | {% for customfield in ticket.ticketcustomfieldvalue_set.all %} 13 | 14 | 15 | 16 | {% endfor %} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% if ticket.resolution %} 26 | 27 | 28 | 29 | 30 | {% endif %} 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 67 | 68 | 69 | 70 |

{{ ticket.id }}. {{ ticket.title }} [{{ ticket.get_status }}]

5 | Edit 6 | | Delete 7 | {% if ticket.on_hold %} | {% trans "Unhold" %}{% else %} | {% trans "Hold" %}{% endif %} 8 |
{% blocktrans with ticket.queue as queue %}Queue: {{ queue }}{% endblocktrans %}
{{ customfield.field.label }}{% ifequal customfield.field.data_type "url" %}{{ customfield.value }}{% else %}{{ customfield.value }}{% endifequal %}
{% trans "Description" %}
{{ ticket.description|force_escape|urlizetrunc:50|linebreaksbr }}
{% trans "Resolution" %}{% ifequal ticket.get_status_display "Resolved" %} {% trans "Accept" %}{% endifequal %}
{{ ticket.resolution|force_escape|urlizetrunc:50|linebreaksbr }}
{% trans "Submitted On" %}{{ ticket.created|date:"r" }} ({{ ticket.created|naturaltime }})
{% trans "Assigned To" %}{{ ticket.get_assigned_to }}{% ifequal ticket.get_assigned_to _('Unassigned') %} {% trans "Take" %}{% endifequal %}
{% trans "Submitter E-Mail" %}{{ ticket.submitter_email }}{% if user.is_superuser %} {% trans "Ignore" %}{% endif %}
{% trans "Priority" %}{{ ticket.get_priority_display }}
{% trans "Copies To" %}{{ ticketcc_string }} {% trans "Manage" %}{% if SHOW_SUBSCRIBE %}, {% trans "Subscribe" %}{% endif %}
{% trans "Dependencies" %}{% for dep in ticket.ticketdependency.all %} 59 | {% if forloop.first %}

{% trans "This ticket cannot be resolved until the following ticket(s) are resolved" %}

{% endif %} 62 | {% empty %} 63 |

{% trans "This ticket has no dependencies." %}

64 | {% endfor %} 65 |

{% trans "Add Dependency" %}

66 |
71 | -------------------------------------------------------------------------------- /helpdesk/tests/ticket_submission.py: -------------------------------------------------------------------------------- 1 | from helpdesk.models import Queue, CustomField, Ticket 2 | from django.test import TestCase 3 | from django.core import mail 4 | from django.test.client import Client 5 | from django.core.urlresolvers import reverse 6 | 7 | class TicketBasicsTestCase(TestCase): 8 | fixtures = ['emailtemplate.json'] 9 | 10 | def setUp(self): 11 | 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') 12 | 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') 13 | 14 | self.ticket_data = { 15 | 'title': 'Test Ticket', 16 | 'description': 'Some Test Ticket', 17 | } 18 | 19 | self.client = Client() 20 | 21 | def test_create_ticket_direct(self): 22 | email_count = len(mail.outbox) 23 | ticket_data = dict(queue=self.queue_public, **self.ticket_data) 24 | ticket = Ticket.objects.create(**ticket_data) 25 | self.assertEqual(ticket.ticket_for_url, "q1-%s" % ticket.id) 26 | self.assertEqual(email_count, len(mail.outbox)) 27 | 28 | 29 | def test_create_ticket_public(self): 30 | email_count = len(mail.outbox) 31 | 32 | response = self.client.get(reverse('helpdesk_home')) 33 | self.assertEqual(response.status_code, 200) 34 | 35 | post_data = { 36 | 'title': 'Test ticket title', 37 | 'queue': self.queue_public.id, 38 | 'submitter_email': 'ticket1.submitter@example.com', 39 | 'body': 'Test ticket body', 40 | 'priority': 3, 41 | } 42 | 43 | response = self.client.post(reverse('helpdesk_home'), post_data, follow=True) 44 | last_redirect = response.redirect_chain[-1] 45 | last_redirect_url = last_redirect[0] 46 | last_redirect_status = last_redirect[1] 47 | # Ensure we landed on the "View" page. 48 | self.assertEqual(last_redirect_url.split('?')[0], 'http://testserver%s' % reverse('helpdesk_public_view')) 49 | # Ensure submitter, new-queue + update-queue were all emailed. 50 | self.assertEqual(email_count+3, len(mail.outbox)) 51 | 52 | def test_create_ticket_private(self): 53 | email_count = len(mail.outbox) 54 | post_data = { 55 | 'title': 'Private ticket test', 56 | 'queue': self.queue_private.id, 57 | 'submitter_email': 'ticket2.submitter@example.com', 58 | 'body': 'Test ticket body', 59 | 'priority': 3, 60 | } 61 | 62 | response = self.client.post(reverse('helpdesk_home'), post_data) 63 | self.assertEqual(response.status_code, 200) 64 | self.assertEqual(email_count, len(mail.outbox)) 65 | self.assertContains(response, 'Select a valid choice.') 66 | 67 | def test_create_ticket_customfields(self): 68 | email_count = len(mail.outbox) 69 | queue_custom = Queue.objects.create(title='Queue 3', slug='q3', allow_public_submission=True, updated_ticket_cc='update.custom@example.com') 70 | custom_field_1 = CustomField.objects.create(name='textfield', label='Text Field', data_type='varchar', max_length=100, ordering=10, required=False, staff_only=False) 71 | post_data = { 72 | 'queue': queue_custom.id, 73 | 'title': 'Ticket with custom text field', 74 | 'submitter_email': 'ticket3.submitter@example.com', 75 | 'body': 'Test ticket body', 76 | 'priority': 3, 77 | 'custom_textfield': 'This is my custom text.', 78 | } 79 | 80 | response = self.client.post(reverse('helpdesk_home'), post_data, follow=True) 81 | 82 | custom_field_1.delete() 83 | last_redirect = response.redirect_chain[-1] 84 | last_redirect_url = last_redirect[0] 85 | last_redirect_status = last_redirect[1] 86 | # Ensure we landed on the "View" page. 87 | self.assertEqual(last_redirect_url.split('?')[0], 'http://testserver%s' % reverse('helpdesk_public_view')) 88 | # Ensure only two e-mails were sent - submitter & updated. 89 | self.assertEqual(email_count+2, len(mail.outbox)) 90 | -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery.jqplot/plugins/jqplot.pointLabels.min.js: -------------------------------------------------------------------------------- 1 | /* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com 2 | jsDate | (c) 2010-2013 Chris Leonello 3 | */(function(c){c.jqplot.PointLabels=function(e){this.show=c.jqplot.config.enablePlugins;this.location="n";this.labelsFromSeries=false;this.seriesLabelIndex=null;this.labels=[];this._labels=[];this.stackedValue=false;this.ypadding=6;this.xpadding=6;this.escapeHTML=true;this.edgeTolerance=-5;this.formatter=c.jqplot.DefaultTickFormatter;this.formatString="";this.hideZeros=false;this._elems=[];c.extend(true,this,e)};var a=["nw","n","ne","e","se","s","sw","w"];var d={nw:0,n:1,ne:2,e:3,se:4,s:5,sw:6,w:7};var b=["se","s","sw","w","nw","n","ne","e"];c.jqplot.PointLabels.init=function(j,h,f,g,i){var e=c.extend(true,{},f,g);e.pointLabels=e.pointLabels||{};if(this.renderer.constructor===c.jqplot.BarRenderer&&this.barDirection==="horizontal"&&!e.pointLabels.location){e.pointLabels.location="e"}this.plugins.pointLabels=new c.jqplot.PointLabels(e.pointLabels);this.plugins.pointLabels.setLabels.call(this)};c.jqplot.PointLabels.prototype.setLabels=function(){var f=this.plugins.pointLabels;var h;if(f.seriesLabelIndex!=null){h=f.seriesLabelIndex}else{if(this.renderer.constructor===c.jqplot.BarRenderer&&this.barDirection==="horizontal"){h=(this._plotData[0].length<3)?0:this._plotData[0].length-1}else{h=(this._plotData.length===0)?0:this._plotData[0].length-1}}f._labels=[];if(f.labels.length===0||f.labelsFromSeries){if(f.stackedValue){if(this._plotData.length&&this._plotData[0].length){for(var e=0;eB||s+C>m){z.remove()}z=null;f=null}}};c.jqplot.postSeriesInitHooks.push(c.jqplot.PointLabels.init);c.jqplot.postDrawSeriesHooks.push(c.jqplot.PointLabels.draw)})(jQuery); -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | django-helpdesk installation isn't difficult, but it requires you have a bit of existing know-how about Django. 5 | 6 | 7 | Getting The Code 8 | ---------------- 9 | 10 | Installing using PIP 11 | ~~~~~~~~~~~~~~~~~~~~ 12 | 13 | Try using ``pip install django-helpdesk``. Go and have a beer to celebrate Python packaging. 14 | 15 | GIT Checkout (Cutting Edge) 16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 17 | 18 | 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`` 19 | 20 | Copy the ``helpdesk`` folder into your ``PYTHONPATH``. 21 | 22 | I just want a .tar.gz! 23 | ~~~~~~~~~~~~~~~~~~~~~~ 24 | 25 | You can download the latest PyPi package from http://pypi.python.org/pypi/django-helpdesk/ 26 | 27 | Download, extract, and drop ``helpdesk`` into your ``PYTHONPATH`` 28 | 29 | Adding To Your Django Project 30 | ----------------------------- 31 | 32 | 1. Edit your ``settings.py`` file and add ``helpdesk`` to the ``INSTALLED_APPS`` setting. You also need ``django.contrib.admin`` in ``INSTALLED_APPS`` if you haven't already added it. eg:: 33 | 34 | INSTALLED_APPS = ( 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.sites', 39 | 'django.contrib.admin', # Required for helpdesk admin/maintenance 40 | 'django.contrib.humanize', # Required for elapsed time formatting 41 | 'south', # Highly recommended to make database migrations simpler in Django < 1.7 42 | 'markdown_deux', # Required for Knowledgebase item formatting 43 | 'bootstrapform', # Required for nicer formatting of forms with the default templates 44 | 'helpdesk', # This is us! 45 | ) 46 | 47 | 2. Make sure django-helpdesk is accessible via ``urls.py``. Add the following line to ``urls.py``:: 48 | 49 | (r'helpdesk/', include('helpdesk.urls')), 50 | 51 | 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:: 52 | 53 | (r'', include('helpdesk.urls')), 54 | 55 | This line will have to come *after* any other lines in your urls.py such as those used by the Django admin. 56 | 57 | 3. Create the required database tables. I'd suggest using *South*, however the following will work:: 58 | 59 | ./manage.py syncdb 60 | 61 | Then migrate using South / Django 1.7+ migrations:: 62 | 63 | ./manage.py migrate helpdesk 64 | 65 | 4. Include your static files in your public web path:: 66 | 67 | python manage.py collectstatic 68 | 69 | 5. Inside your ``MEDIA_ROOT`` folder, inside the ``helpdesk`` folder, is a folder called ``attachments``. Ensure your web server software can write to this folder - something like this should do the trick:: 70 | 71 | chown www-data:www-data attachments/ 72 | chmod 700 attachments 73 | 74 | (substitute www-data for the user / group that your web server runs as, eg 'apache' or 'httpd') 75 | 76 | If all else fails ensure all users can write to it:: 77 | 78 | chmod 777 attachments/ 79 | 80 | This is NOT recommended, especially if you're on a shared server. 81 | 82 | 6. Ensure that your ``attachments`` folder has directory listings turned off, to ensure users don't download files that they are not specifically linked to from their tickets. 83 | 84 | If you are using Apache, put a ``.htaccess`` file in the ``attachments`` folder with the following content:: 85 | 86 | Options -Indexes 87 | 88 | You will also have to make sure that ``.htaccess`` files aren't being ignored. 89 | 90 | Ideally, accessing http://MEDIA_URL/helpdesk/attachments/ will give you a 403 access denied error. 91 | 92 | 7. If it's not already installed, install ``django-markdown-deux`` and ensure it's in your ``INSTALLED_APPS``:: 93 | 94 | pip install django-markdown-deux 95 | 96 | 8. If you already have a view handling your logins, then great! If not, add the following to ``settings.py`` to get your Django installation to use the login view included in ``django-helpdesk``:: 97 | 98 | LOGIN_URL = '/helpdesk/login/' 99 | 100 | Alter the URL to suit your installation path. 101 | 102 | 9. Load initial e-mail templates, otherwise you will not be able to setnd e-mail:: 103 | 104 | python manage.py loaddata emailtemplate.json 105 | -------------------------------------------------------------------------------- /helpdesk/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Default settings for django-helpdesk. 3 | 4 | """ 5 | 6 | from django.conf import settings 7 | 8 | 9 | try: 10 | DEFAULT_USER_SETTINGS = settings.HELPDESK_DEFAULT_SETTINGS 11 | except: 12 | DEFAULT_USER_SETTINGS = None 13 | 14 | if type(DEFAULT_USER_SETTINGS) != type(dict()): 15 | DEFAULT_USER_SETTINGS = { 16 | 'use_email_as_submitter': True, 17 | 'email_on_ticket_assign': True, 18 | 'email_on_ticket_change': True, 19 | 'login_view_ticketlist': True, 20 | 'email_on_ticket_apichange': True, 21 | 'tickets_per_page': 25 22 | } 23 | 24 | 25 | HAS_TAG_SUPPORT = False 26 | 27 | ''' generic options - visible on all pages ''' 28 | # redirect to login page instead of the default homepage when users visits "/"? 29 | HELPDESK_REDIRECT_TO_LOGIN_BY_DEFAULT = getattr(settings, 'HELPDESK_REDIRECT_TO_LOGIN_BY_DEFAULT', False) 30 | 31 | # show knowledgebase links? 32 | HELPDESK_KB_ENABLED = getattr(settings, 'HELPDESK_KB_ENABLED', True) 33 | 34 | # show extended navigation by default, to all users, irrespective of staff status? 35 | HELPDESK_NAVIGATION_ENABLED = getattr(settings, 'HELPDESK_NAVIGATION_ENABLED', False) 36 | 37 | # show dropdown list of languages that ticket comments can be translated into? 38 | HELPDESK_TRANSLATE_TICKET_COMMENTS = getattr(settings, 'HELPDESK_TRANSLATE_TICKET_COMMENTS', False) 39 | 40 | # list of languages to offer. if set to false, all default google translate languages will be shown. 41 | HELPDESK_TRANSLATE_TICKET_COMMENTS_LANG = getattr(settings, 'HELPDESK_TRANSLATE_TICKET_COMMENTS_LANG', ["en", "de", "fr", "it", "ru"]) 42 | 43 | # show link to 'change password' on 'User Settings' page? 44 | HELPDESK_SHOW_CHANGE_PASSWORD = getattr(settings, 'HELPDESK_SHOW_CHANGE_PASSWORD', False) 45 | 46 | # allow user to override default layout for 'followups' - work in progress. 47 | HELPDESK_FOLLOWUP_MOD = getattr(settings, 'HELPDESK_FOLLOWUP_MOD', False) 48 | 49 | # auto-subscribe user to ticket if (s)he responds to a ticket? 50 | HELPDESK_AUTO_SUBSCRIBE_ON_TICKET_RESPONSE = getattr(settings, 'HELPDESK_AUTO_SUBSCRIBE_ON_TICKET_RESPONSE', False) 51 | 52 | 53 | ''' options for public pages ''' 54 | # show 'view a ticket' section on public page? 55 | HELPDESK_VIEW_A_TICKET_PUBLIC = getattr(settings, 'HELPDESK_VIEW_A_TICKET_PUBLIC', True) 56 | 57 | # show 'submit a ticket' section on public page? 58 | HELPDESK_SUBMIT_A_TICKET_PUBLIC = getattr(settings, 'HELPDESK_SUBMIT_A_TICKET_PUBLIC', True) 59 | 60 | 61 | 62 | ''' options for update_ticket views ''' 63 | # allow non-staff users to interact with tickets? this will also change how 'staff_member_required' 64 | # in staff.py will be defined. 65 | HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE = getattr(settings, 'HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE', False) 66 | 67 | # show edit buttons in ticket follow ups. 68 | HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP = getattr(settings, 'HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP', True) 69 | 70 | # show delete buttons in ticket follow ups if user is 'superuser' 71 | HELPDESK_SHOW_DELETE_BUTTON_SUPERUSER_FOLLOW_UP = getattr(settings, 'HELPDESK_SHOW_DELETE_BUTTON_SUPERUSER_FOLLOW_UP', False) 72 | 73 | # make all updates public by default? this will hide the 'is this update public' checkbox 74 | HELPDESK_UPDATE_PUBLIC_DEFAULT = getattr(settings, 'HELPDESK_UPDATE_PUBLIC_DEFAULT', False) 75 | 76 | # only show staff users in ticket owner drop-downs 77 | HELPDESK_STAFF_ONLY_TICKET_OWNERS = getattr(settings, 'HELPDESK_STAFF_ONLY_TICKET_OWNERS', False) 78 | 79 | # only show staff users in ticket cc drop-down 80 | HELPDESK_STAFF_ONLY_TICKET_CC = getattr(settings, 'HELPDESK_STAFF_ONLY_TICKET_CC', False) 81 | 82 | 83 | # allow the subject to have a configurable template. 84 | HELPDESK_EMAIL_SUBJECT_TEMPLATE = getattr(settings, 'HELPDESK_EMAIL_SUBJECT_TEMPLATE', "{{ ticket.ticket }} {{ ticket.title|safe }} %(subject)s") 85 | 86 | 87 | ''' options for staff.create_ticket view ''' 88 | # hide the 'assigned to' / 'Case owner' field from the 'create_ticket' view? 89 | HELPDESK_CREATE_TICKET_HIDE_ASSIGNED_TO = getattr(settings, 'HELPDESK_CREATE_TICKET_HIDE_ASSIGNED_TO', False) 90 | 91 | 92 | 93 | ''' email options ''' 94 | # default Queue email submission settings 95 | QUEUE_EMAIL_BOX_TYPE = getattr(settings, 'QUEUE_EMAIL_BOX_TYPE', None) 96 | QUEUE_EMAIL_BOX_SSL = getattr(settings, 'QUEUE_EMAIL_BOX_SSL', None) 97 | QUEUE_EMAIL_BOX_HOST = getattr(settings, 'QUEUE_EMAIL_BOX_HOST', None) 98 | QUEUE_EMAIL_BOX_USER = getattr(settings, 'QUEUE_EMAIL_BOX_USER', None) 99 | QUEUE_EMAIL_BOX_PASSWORD = getattr(settings, 'QUEUE_EMAIL_BOX_PASSWORD', None) 100 | -------------------------------------------------------------------------------- /helpdesk/static/helpdesk/jquery.jqplot/plugins/jqplot.enhancedLegendRenderer.min.js: -------------------------------------------------------------------------------- 1 | /* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com 2 | jsDate | (c) 2010-2013 Chris Leonello 3 | */(function(c){c.jqplot.EnhancedLegendRenderer=function(){c.jqplot.TableLegendRenderer.call(this)};c.jqplot.EnhancedLegendRenderer.prototype=new c.jqplot.TableLegendRenderer();c.jqplot.EnhancedLegendRenderer.prototype.constructor=c.jqplot.EnhancedLegendRenderer;c.jqplot.EnhancedLegendRenderer.prototype.init=function(d){this.numberRows=null;this.numberColumns=null;this.seriesToggle="normal";this.seriesToggleReplot=false;this.disableIEFading=true;c.extend(true,this,d);if(this.seriesToggle){c.jqplot.postDrawHooks.push(b)}};c.jqplot.EnhancedLegendRenderer.prototype.draw=function(m,y){var f=this;if(this.show){var r=this._series;var u;var w="position:absolute;";w+=(this.background)?"background:"+this.background+";":"";w+=(this.border)?"border:"+this.border+";":"";w+=(this.fontSize)?"font-size:"+this.fontSize+";":"";w+=(this.fontFamily)?"font-family:"+this.fontFamily+";":"";w+=(this.textColor)?"color:"+this.textColor+";":"";w+=(this.marginTop!=null)?"margin-top:"+this.marginTop+";":"";w+=(this.marginBottom!=null)?"margin-bottom:"+this.marginBottom+";":"";w+=(this.marginLeft!=null)?"margin-left:"+this.marginLeft+";":"";w+=(this.marginRight!=null)?"margin-right:"+this.marginRight+";":"";this._elem=c('
');if(this.seriesToggle){this._elem.css("z-index","3")}var C=false,q=false,d,o;if(this.numberRows){d=this.numberRows;if(!this.numberColumns){o=Math.ceil(r.length/d)}else{o=this.numberColumns}}else{if(this.numberColumns){o=this.numberColumns;d=Math.ceil(r.length/this.numberColumns)}else{d=r.length;o=1}}var B,z,e,l,k,n,p,t,h,g;var v=0;for(B=r.length-1;B>=0;B--){if(o==1&&r[B]._stack||r[B].renderer.constructor==c.jqplot.BezierCurveRenderer){q=true}}for(B=0;B0){C=true}else{C=false}}else{if(B==d-1){C=false}else{C=true}}p=(C)?this.rowSpacing:"0";l=c(document.createElement("td"));l.addClass("jqplot-table-legend jqplot-table-legend-swatch");l.css({textAlign:"center",paddingTop:p});h=c(document.createElement("div"));h.addClass("jqplot-table-legend-swatch-outline");g=c(document.createElement("div"));g.addClass("jqplot-table-legend-swatch");g.css({backgroundColor:x,borderColor:x});l.append(h.append(g));k=c(document.createElement("td"));k.addClass("jqplot-table-legend jqplot-table-legend-label");k.css("paddingTop",p);if(this.escapeHtml){k.text(n)}else{k.html(n)}if(q){if(this.showLabels){k.prependTo(e)}if(this.showSwatches){l.prependTo(e)}}else{if(this.showSwatches){l.appendTo(e)}if(this.showLabels){k.appendTo(e)}}if(this.seriesToggle){var A;if(typeof(this.seriesToggle)==="string"||typeof(this.seriesToggle)==="number"){if(!c.jqplot.use_excanvas||!this.disableIEFading){A=this.seriesToggle}}if(this.showSwatches){l.bind("click",{series:u,speed:A,plot:y,replot:this.seriesToggleReplot},a);l.addClass("jqplot-seriesToggle")}if(this.showLabels){k.bind("click",{series:u,speed:A,plot:y,replot:this.seriesToggleReplot},a);k.addClass("jqplot-seriesToggle")}if(!u.show&&u.showLabel){l.addClass("jqplot-series-hidden");k.addClass("jqplot-series-hidden")}}C=true}}v++}l=k=h=g=null}}return this._elem};var a=function(j){var i=j.data,m=i.series,k=i.replot,h=i.plot,f=i.speed,l=m.index,g=false;if(m.canvas._elem.is(":hidden")||!m.show){g=true}var e=function(){if(k){var n={};if(c.isPlainObject(k)){c.extend(true,n,k)}h.replot(n);if(g&&f){var d=h.series[l];if(d.shadowCanvas._elem){d.shadowCanvas._elem.hide().fadeIn(f)}d.canvas._elem.hide().fadeIn(f);d.canvas._elem.nextAll(".jqplot-point-label.jqplot-series-"+d.index).hide().fadeIn(f)}}else{var d=h.series[l];if(d.canvas._elem.is(":hidden")||!d.show){if(typeof h.options.legend.showSwatches==="undefined"||h.options.legend.showSwatches===true){h.legend._elem.find("td").eq(l*2).addClass("jqplot-series-hidden")}if(typeof h.options.legend.showLabels==="undefined"||h.options.legend.showLabels===true){h.legend._elem.find("td").eq((l*2)+1).addClass("jqplot-series-hidden")}}else{if(typeof h.options.legend.showSwatches==="undefined"||h.options.legend.showSwatches===true){h.legend._elem.find("td").eq(l*2).removeClass("jqplot-series-hidden")}if(typeof h.options.legend.showLabels==="undefined"||h.options.legend.showLabels===true){h.legend._elem.find("td").eq((l*2)+1).removeClass("jqplot-series-hidden")}}}};m.toggleDisplay(j,e)};var b=function(){if(this.legend.renderer.constructor==c.jqplot.EnhancedLegendRenderer&&this.legend.seriesToggle){var d=this.legend._elem.detach();this.eventCanvas._elem.after(d)}}})(jQuery); -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/report_output.html: -------------------------------------------------------------------------------- 1 | {% extends "helpdesk/base.html" %}{% load i18n %} 2 | 3 | {% block helpdesk_title %}{% trans "Reports & Statistics" %}{% endblock %} 4 | 5 | {% block helpdesk_head %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% endblock %} 14 | 15 | {% block helpdesk_body %} 16 |

{% trans "Reports & Statistics" %}

17 | 18 | {% if user_saved_queries_ %} 19 |

{% trans "You can run this query on filtered data by using one of your saved queries." %}

20 |
21 | 22 | 26 | 27 |
28 | {% else %} 29 |

{% trans "Want to filter this report to just show a subset of data? Go to the Ticket List, filter your query, and save your query." %}

30 | {% endif %} 31 | 32 | 33 | 34 | 35 | {% for h in headings %}{% endfor %} 36 | 37 | 38 | {% for d in data %} 39 | {% for f in d %}{% endfor %}{% endfor %} 40 | 41 |
{{ title }}
{% if forloop.first %}{{ h|title }}{% else %}{{ h }}{% endif %}
{{ f }}
42 | 43 |
44 | {% ifequal charttype "date" %} 45 | 73 | {% endifequal %} 74 | {% ifequal charttype "bar" %} 75 | 104 | {% endifequal %} 105 | 106 | 107 | 108 | {% endblock %} 109 | -------------------------------------------------------------------------------- /LICENSE.3RDPARTY: -------------------------------------------------------------------------------- 1 | This file contains license details for 3rd party software which is 2 | distributed with Jutda Helpdesk. 3 | 4 | 1. License for jQuery & jQuery UI 5 | 2. License for jQuery UI 'Smoothness' theme 6 | 3. License for akismet.py 7 | 4. License for jqPlot 8 | 9 | ---------------------------------------------------------------------- 10 | 11 | 1. License for jQuery & jQuery UI 12 | 13 | Copyright (c) 2007 John Resig, http://jquery.com/ 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining 16 | a copy of this software and associated documentation files (the 17 | "Software"), to deal in the Software without restriction, including 18 | without limitation the rights to use, copy, modify, merge, publish, 19 | distribute, sublicense, and/or sell copies of the Software, and to 20 | permit persons to whom the Software is furnished to do so, subject to 21 | the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be 24 | included in all copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 28 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 29 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 30 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 31 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 32 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | 34 | ---------------------------------------------------------------------- 35 | 36 | 2. License for jQuery UI 'Smoothness' theme 37 | 38 | /* 39 | * jQuery UI screen structure and presentation 40 | * This CSS file was generated by ThemeRoller, a Filament Group Project for jQuery UI 41 | * Author: Scott Jehl, scott@filamentgroup.com, http://www.filamentgroup.com 42 | * Visit ThemeRoller.com 43 | */ 44 | 45 | ---------------------------------------------------------------------- 46 | 47 | 3. License for akismet.py 48 | 49 | Copyright (c) 2003-2009, Michael Foord 50 | All rights reserved. 51 | E-mail : fuzzyman AT voidspace DOT org DOT uk 52 | 53 | Redistribution and use in source and binary forms, with or without 54 | modification, are permitted provided that the following conditions are 55 | met: 56 | 57 | 58 | * Redistributions of source code must retain the above copyright 59 | notice, this list of conditions and the following disclaimer. 60 | 61 | * Redistributions in binary form must reproduce the above 62 | copyright notice, this list of conditions and the following 63 | disclaimer in the documentation and/or other materials provided 64 | with the distribution. 65 | 66 | * Neither the name of Michael Foord nor the name of Voidspace 67 | may be used to endorse or promote products derived from this 68 | software without specific prior written permission. 69 | 70 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 71 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 72 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 73 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 74 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 75 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 76 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 77 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 78 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 79 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 80 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 81 | 82 | ---------------------------------------------------------------------- 83 | 84 | 4. License for jqPlot 85 | 86 | Copyright (c) 2009 - 2010 Chris Leonello 87 | 88 | Permission is hereby granted, free of charge, to any person obtaining a copy 89 | of this software and associated documentation files (the "Software"), to deal 90 | in the Software without restriction, including without limitation the rights 91 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 92 | copies of the Software, and to permit persons to whom the Software is 93 | furnished to do so, subject to the following conditions: 94 | 95 | The above copyright notice and this permission notice shall be included in 96 | all copies or substantial portions of the Software. 97 | 98 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 99 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 100 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 101 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 102 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 103 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 104 | THE SOFTWARE. 105 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = . 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-helpdesk.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-helpdesk.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django-helpdesk" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-helpdesk" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | django-helpdesk - A Django powered ticket tracker for small enterprise. 2 | ======================================================================= 3 | 4 | .. image:: https://travis-ci.org/rossp/django-helpdesk.png?branch=master 5 | :target: https://travis-ci.org/rossp/django-helpdesk 6 | 7 | Copyright 2009-11 Jutda and Ross Poulton. All Rights Reserved. See LICENSE for details. 8 | 9 | django-helpdesk was formerly known as Jutda Helpdesk, named after the 10 | company who originally created it. As of January 2011 the name has been 11 | changed to reflect what it really is: a Django-powered ticket tracker with 12 | contributors reaching far beyond Jutda. 13 | 14 | Complete documentation is available in the docs/ directory, or online at http://django-helpdesk.readthedocs.org/. 15 | 16 | You can see a demo installation at http://django-helpdesk-demo.herokuapp.com/ 17 | 18 | Licensing 19 | --------- 20 | 21 | See the file 'LICENSE' for licensing terms. Note that django-helpdesk is 22 | distributed with 3rd party products which have their own licenses. See 23 | LICENSE.3RDPARTY for license terms for included packages. 24 | 25 | Dependencies (pre-flight checklist) 26 | ----------------------------------- 27 | 28 | 1. Python 2.6+ 29 | 2. Django (1.4 or newer) 30 | 3. South for database migrations (highly recommended, but not required). Download from http://south.aeracode.org/ 31 | 4. An existing WORKING Django project with database etc. If you 32 | cannot log into the Admin, you won't get this product working. 33 | 5. `pip install django-bootstrap-form` and add `bootstrapform` to `settings.INSTALLED_APPS` 34 | 6. `pip install django-markdown-deux` and add `markdown_deux` to `settings.INSTALLED_APPS` 35 | 7. `pip install email-reply-parser` to get smart email reply handling 36 | 37 | **NOTE REGARDING SQLITE AND SEARCHING:** 38 | If you use sqlite as your database, the search function will not work as 39 | effectively as it will with other databases due to its inability to do 40 | case-insensitive searches. It's recommended that you use PostgreSQL or MySQL 41 | if possible. For more information, see this note in the Django documentation: 42 | http://docs.djangoproject.com/en/dev/ref/databases/#sqlite-string-matching 43 | 44 | When you try to do a keyword search using sqlite, a message will be displayed 45 | to alert you to this shortcoming. There is no way around it, sorry. 46 | 47 | **NOTE REGARDING MySQL:** 48 | If you use MySQL, with most default configurations you will receive an error 49 | when creating the database tables as we populate a number of default templates 50 | in languages other than English. 51 | 52 | You must create the database the holds the django-helpdesk tables using the 53 | UTF-8 collation; see the MySQL manual for more information: 54 | http://dev.mysql.com/doc/refman/5.1/en/charset-database.html 55 | 56 | If you do NOT do this step, and you only want to use English-language templates, 57 | you can continue however you will receive a warning when running the 'migrate' 58 | commands. 59 | 60 | Fresh Django Installations 61 | -------------------------- 62 | 63 | If you're on a brand new Django installation, make sure you do a ``syncdb`` 64 | **before** adding ``helpdesk`` to your ``INSTALLED_APPS``. This will avoid 65 | errors with trying to create User settings. 66 | 67 | Upgrading from previous versions 68 | -------------------------------- 69 | 70 | We highly recommend that you use South (available 71 | from http://south.aeracode.org/) to assist with management of database schema 72 | changes. 73 | 74 | If you are upgrading from a previous version that did NOT use South for 75 | migrations (i.e. prior to April 2011) then you will need to 'fake' the first 76 | migration:: 77 | 78 | python manage.py migrate helpdesk 0001 --fake 79 | 80 | If you are upgrading from a previous version of django-helpdesk that DID use 81 | South, simply download an up to date version of the code base (eg by using 82 | `git pull` or `pip install --upgrade django-helpdesk`) then migrate the database:: 83 | 84 | python manage.py migrate helpdesk --db-dry-run # DB untouched 85 | python manage.py migrate helpdesk 86 | 87 | Lastly, restart your web server software (eg Apache) or FastCGI instance, to 88 | ensure the latest changes are in use. 89 | 90 | You can continue to the 'Initial Configuration' area, if needed. 91 | 92 | Installation 93 | ------------ 94 | 95 | ``pip install django-helpdesk`` 96 | 97 | For further installation information see docs/install.html and docs/configuration.html 98 | 99 | Contributing 100 | ------------ 101 | 102 | If you want to help translate django-helpdesk into languages other than English, we encourage you to make use of our Transifex project. 103 | 104 | http://www.transifex.net/projects/p/django-helpdesk/resource/core/ 105 | 106 | Feel free to request access to contribute your translations. 107 | 108 | 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. 109 | 110 | .. image:: https://secure.travis-ci.org/rossp/django-helpdesk.png?branch=master 111 | :target: https://travis-ci.org/rossp/django-helpdesk 112 | -------------------------------------------------------------------------------- /helpdesk/south_migrations/0011_populate_usersettings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import DataMigration 5 | from django.db import models 6 | from django.contrib.auth import get_user_model 7 | from helpdesk.settings import DEFAULT_USER_SETTINGS 8 | 9 | 10 | def pickle_settings(data): 11 | """Pickling as defined at migration's creation time""" 12 | import cPickle 13 | from helpdesk.lib import b64encode 14 | return b64encode(cPickle.dumps(data)) 15 | 16 | 17 | # https://docs.djangoproject.com/en/1.7/topics/migrations/#data-migrations 18 | def populate_usersettings(orm): 19 | """Create a UserSettings entry for each existing user. 20 | This will only happen once (at install time, or at upgrade) 21 | when the UserSettings model doesn't already exist.""" 22 | 23 | _User = get_user_model() 24 | 25 | # Import historical version of models 26 | User = orm[_User._meta.app_label+'.'+_User._meta.model_name] 27 | UserSettings = orm["helpdesk"+'.'+"UserSettings"] 28 | settings_pickled = pickle_settings(DEFAULT_USER_SETTINGS) 29 | 30 | for u in User.objects.all(): 31 | try: 32 | UserSettings.objects.get(user=u) 33 | except UserSettings.DoesNotExist: 34 | UserSettings.objects.create(user=u, settings_pickled=settings_pickled) 35 | 36 | class Migration(DataMigration): 37 | 38 | def forwards(self, orm): 39 | populate_usersettings(orm) 40 | 41 | def backwards(self, orm): 42 | pass 43 | 44 | models = { 45 | u'auth.group': { 46 | 'Meta': {'object_name': 'Group'}, 47 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 48 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 49 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 50 | }, 51 | u'auth.permission': { 52 | 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, 53 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 54 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 55 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 56 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 57 | }, 58 | u'auth.user': { 59 | 'Meta': {'object_name': 'User'}, 60 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 61 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 62 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 63 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), 64 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 65 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 66 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 67 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 68 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 69 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 70 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 71 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), 72 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 73 | }, 74 | u'contenttypes.contenttype': { 75 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 76 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 77 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 78 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 79 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 80 | }, 81 | u'helpdesk.usersettings': { 82 | 'Meta': {'object_name': 'UserSettings'}, 83 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 84 | 'settings_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 85 | 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'}) 86 | } 87 | } 88 | 89 | complete_apps = ['helpdesk'] 90 | symmetrical = True 91 | -------------------------------------------------------------------------------- /helpdesk/management/commands/create_escalation_exclusions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Jutda Helpdesk - A Django powered ticket tracker for small enterprise. 4 | 5 | (c) Copyright 2008 Jutda. All Rights Reserved. See LICENSE for details. 6 | 7 | scripts/create_escalation_exclusion.py - Easy way to routinely add particular 8 | days to the list of days on which no 9 | escalation should take place. 10 | """ 11 | 12 | from datetime import timedelta, date 13 | import getopt 14 | from optparse import make_option 15 | import sys 16 | 17 | from django.core.management.base import BaseCommand, CommandError 18 | from django.db.models import Q 19 | 20 | from helpdesk.models import EscalationExclusion, Queue 21 | 22 | 23 | class Command(BaseCommand): 24 | def __init__(self): 25 | BaseCommand.__init__(self) 26 | 27 | self.option_list += ( 28 | make_option( 29 | '--days', '-d', 30 | help='Days of week (monday, tuesday, etc)'), 31 | make_option( 32 | '--occurrences', '-o', 33 | type='int', 34 | default=1, 35 | help='Occurrences: How many weeks ahead to exclude this day'), 36 | make_option( 37 | '--queues', '-q', 38 | help='Queues to include (default: all). Use queue slugs'), 39 | make_option( 40 | '--escalate-verbosely', '-x', 41 | action='store_true', 42 | default=False, 43 | help='Display a list of dates excluded'), 44 | ) 45 | 46 | def handle(self, *args, **options): 47 | days = options['days'] 48 | occurrences = options['occurrences'] 49 | verbose = False 50 | queue_slugs = options['queues'] 51 | queues = [] 52 | 53 | if options['escalate-verbosely']: 54 | verbose = True 55 | 56 | # this should already be handled by optparse 57 | if not occurrences: occurrences = 1 58 | if not (days and occurrences): 59 | raise CommandError('One or more occurrences must be specified.') 60 | 61 | if queue_slugs is not None: 62 | queue_set = queue_slugs.split(',') 63 | for queue in queue_set: 64 | try: 65 | q = Queue.objects.get(slug__exact=queue) 66 | except Queue.DoesNotExist: 67 | raise CommandError("Queue %s does not exist." % queue) 68 | queues.append(q) 69 | 70 | create_exclusions(days=days, occurrences=occurrences, verbose=verbose, queues=queues) 71 | 72 | 73 | day_names = { 74 | 'monday': 0, 75 | 'tuesday': 1, 76 | 'wednesday': 2, 77 | 'thursday': 3, 78 | 'friday': 4, 79 | 'saturday': 5, 80 | 'sunday': 6, 81 | } 82 | 83 | 84 | def create_exclusions(days, occurrences, verbose, queues): 85 | days = days.split(',') 86 | for day in days: 87 | day_name = day 88 | day = day_names[day] 89 | workdate = date.today() 90 | i = 0 91 | while i < occurrences: 92 | if day == workdate.weekday(): 93 | if EscalationExclusion.objects.filter(date=workdate).count() == 0: 94 | esc = EscalationExclusion(name='Auto Exclusion for %s' % day_name, date=workdate) 95 | esc.save() 96 | 97 | if verbose: 98 | print "Created exclusion for %s %s" % (day_name, workdate) 99 | 100 | for q in queues: 101 | esc.queues.add(q) 102 | if verbose: 103 | print " - for queue %s" % q 104 | 105 | i += 1 106 | workdate += timedelta(days=1) 107 | 108 | 109 | def usage(): 110 | print "Options:" 111 | print " --days, -d: Days of week (monday, tuesday, etc)" 112 | print " --occurrences, -o: Occurrences: How many weeks ahead to exclude this day" 113 | print " --queues, -q: Queues to include (default: all). Use queue slugs" 114 | print " --verbose, -v: Display a list of dates excluded" 115 | 116 | 117 | if __name__ == '__main__': 118 | # This script can be run from the command-line or via Django's manage.py. 119 | try: 120 | opts, args = getopt.getopt(sys.argv[1:], 'd:o:q:v', ['days=', 'occurrences=', 'verbose', 'queues=']) 121 | except getopt.GetoptError: 122 | usage() 123 | sys.exit(2) 124 | 125 | days = None 126 | occurrences = None 127 | verbose = False 128 | queue_slugs = None 129 | queues = [] 130 | 131 | for o, a in opts: 132 | if o in ('-x', '--escalate-verbosely'): 133 | verbose = True 134 | if o in ('-d', '--days'): 135 | days = a 136 | if o in ('-q', '--queues'): 137 | queue_slugs = a 138 | if o in ('-o', '--occurrences'): 139 | occurrences = int(a) 140 | 141 | if not occurrences: occurrences = 1 142 | if not (days and occurrences): 143 | usage() 144 | sys.exit(2) 145 | 146 | if queue_slugs is not None: 147 | queue_set = queue_slugs.split(',') 148 | for queue in queue_set: 149 | try: 150 | q = Queue.objects.get(slug__exact=queue) 151 | except Queue.DoesNotExist: 152 | print "Queue %s does not exist." % queue 153 | sys.exit(2) 154 | queues.append(q) 155 | 156 | create_exclusions(days=days, occurrences=occurrences, verbose=verbose, queues=queues) 157 | -------------------------------------------------------------------------------- /helpdesk/templates/helpdesk/navigation.html: -------------------------------------------------------------------------------- 1 | {% load i18n %}{% load url from future %} 2 | 3 | 79 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from distutils.util import convert_path 4 | from fnmatch import fnmatchcase 5 | from setuptools import setup, find_packages 6 | 7 | version = '0.1.16' 8 | 9 | # Provided as an attribute, so you can append to these instead 10 | # of replicating them: 11 | standard_exclude = ('*.py', '*.pyc', '*$py.class', '*~', '.*', '*.bak') 12 | standard_exclude_directories = ('.*', 'CVS', '_darcs', './build', 13 | './dist', 'EGG-INFO', '*.egg-info') 14 | 15 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 16 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 17 | # Note: you may want to copy this into your setup.py file verbatim, as 18 | # you can't import this from another package, when you don't know if 19 | # that package is installed yet. 20 | def find_package_data( 21 | where='.', package='', 22 | exclude=standard_exclude, 23 | exclude_directories=standard_exclude_directories, 24 | only_in_packages=True, 25 | show_ignored=False): 26 | """ 27 | Return a dictionary suitable for use in ``package_data`` 28 | in a distutils ``setup.py`` file. 29 | 30 | The dictionary looks like:: 31 | 32 | {'package': [files]} 33 | 34 | Where ``files`` is a list of all the files in that package that 35 | don't match anything in ``exclude``. 36 | 37 | If ``only_in_packages`` is true, then top-level directories that 38 | are not packages won't be included (but directories under packages 39 | will). 40 | 41 | Directories matching any pattern in ``exclude_directories`` will 42 | be ignored; by default directories with leading ``.``, ``CVS``, 43 | and ``_darcs`` will be ignored. 44 | 45 | If ``show_ignored`` is true, then all the files that aren't 46 | included in package data are shown on stderr (for debugging 47 | purposes). 48 | 49 | Note patterns use wildcards, or can be exact paths (including 50 | leading ``./``), and all searching is case-insensitive. 51 | """ 52 | 53 | out = {} 54 | stack = [(convert_path(where), '', package, only_in_packages)] 55 | while stack: 56 | where, prefix, package, only_in_packages = stack.pop(0) 57 | for name in os.listdir(where): 58 | fn = os.path.join(where, name) 59 | if os.path.isdir(fn): 60 | bad_name = False 61 | for pattern in exclude_directories: 62 | if (fnmatchcase(name, pattern) 63 | or fn.lower() == pattern.lower()): 64 | bad_name = True 65 | if show_ignored: 66 | print >> sys.stderr, ( 67 | "Directory %s ignored by pattern %s" 68 | % (fn, pattern)) 69 | break 70 | if bad_name: 71 | continue 72 | if (os.path.isfile(os.path.join(fn, '__init__.py')) 73 | and not prefix): 74 | if not package: 75 | new_package = name 76 | else: 77 | new_package = package + '.' + name 78 | stack.append((fn, '', new_package, False)) 79 | else: 80 | stack.append((fn, prefix + name + '/', package, only_in_packages)) 81 | elif package or not only_in_packages: 82 | # is a file 83 | bad_name = False 84 | for pattern in exclude: 85 | if (fnmatchcase(name, pattern) 86 | or fn.lower() == pattern.lower()): 87 | bad_name = True 88 | if show_ignored: 89 | print >> sys.stderr, ( 90 | "File %s ignored by pattern %s" 91 | % (fn, pattern)) 92 | break 93 | if bad_name: 94 | continue 95 | out.setdefault(package, []).append(prefix+name) 96 | return out 97 | 98 | 99 | def get_requirements(): 100 | with open(os.path.join(os.path.dirname(__file__), "requirements.txt")) as f: 101 | requirements_list = [req.strip() for req in f.readlines()] 102 | 103 | requirements_list.append("setuptools") 104 | requirements_list.append("pytz") 105 | return requirements_list 106 | 107 | 108 | LONG_DESCRIPTION = """ 109 | =============== 110 | django-helpdesk 111 | =============== 112 | 113 | This is a Django-powered helpdesk ticket tracker, designed to 114 | plug into an existing Django website and provide you with 115 | internal (or, perhaps, external) helpdesk management. 116 | """ 117 | 118 | setup( 119 | name='django-helpdesk', 120 | version=version, 121 | description="Django-powered ticket tracker for your helpdesk", 122 | long_description=LONG_DESCRIPTION, 123 | classifiers=[ 124 | "Programming Language :: Python", 125 | "Topic :: Software Development :: Libraries :: Python Modules", 126 | "Framework :: Django", 127 | "Environment :: Web Environment", 128 | "Operating System :: OS Independent", 129 | "Intended Audience :: Customer Service", 130 | "License :: OSI Approved :: BSD License", 131 | "Natural Language :: English", 132 | "Topic :: Office/Business", 133 | "Topic :: Software Development :: Bug Tracking", 134 | ], 135 | keywords=['django', 'helpdesk', 'tickets', 'incidents', 'cases'], 136 | author='Ross Poulton', 137 | author_email='ross@rossp.org', 138 | url='http://github.com/rossp/django-helpdesk', 139 | license='BSD', 140 | packages=find_packages(), 141 | package_data=find_package_data("helpdesk", only_in_packages=False), 142 | include_package_data=True, 143 | zip_safe=False, 144 | install_requires=get_requirements(), 145 | ) 146 | 147 | --------------------------------------------------------------------------------