Merge 0.2.7 bugfixes

This commit is contained in:
Garret Wassermann 2018-03-04 04:01:48 -05:00
commit 54a6b1d21b
19 changed files with 149 additions and 27 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
*.pyc *.pyc
.eggs/
/dist/ /dist/
django_helpdesk.egg-info django_helpdesk.egg-info
demo/*.egg-info demo/*.egg-info

View File

@ -7,7 +7,7 @@ python:
- "3.6" - "3.6"
env: env:
- DJANGO=1.11.8 - DJANGO=1.11.9
install: install:
- pip install -q Django==$DJANGO - pip install -q Django==$DJANGO

View File

@ -1,4 +1,5 @@
Copyright (c) 2008, Ross Poulton (Trading as Jutda) Copyright (c) 2008 Ross Poulton (Trading as Jutda),
Copyright (c) 2008-2018 django-helpdesk contributors.
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,

View File

@ -7,7 +7,7 @@ django-helpdesk - A Django powered ticket tracker for small businesses.
.. image:: https://codecov.io/gh/django-helpdesk/django-helpdesk/branch/master/graph/badge.svg .. image:: https://codecov.io/gh/django-helpdesk/django-helpdesk/branch/master/graph/badge.svg
:target: https://codecov.io/gh/django-helpdesk/django-helpdesk :target: https://codecov.io/gh/django-helpdesk/django-helpdesk
Copyright 2009-2017 Ross Poulton and django-helpdesk contributors. All Rights Reserved. Copyright 2009-2018 Ross Poulton and django-helpdesk contributors. All Rights Reserved.
See LICENSE for details. See LICENSE for details.
django-helpdesk was formerly known as Jutda Helpdesk, named after the django-helpdesk was formerly known as Jutda Helpdesk, named after the
@ -62,7 +62,7 @@ Installation
`django-helpdesk` requires: `django-helpdesk` requires:
* Django 1.11.x *only* * Django 1.11.x
* either Python 2.7 or 3.4+ * either Python 2.7 or 3.4+
**NOTE REGARDING PYTHON VERSION:** **NOTE REGARDING PYTHON VERSION:**
@ -72,6 +72,11 @@ and Django itself (Django 2.0), so users and developers are encouraged to begin
transitioning to Python 3 if have not already. New projects should definitely transitioning to Python 3 if have not already. New projects should definitely
use Python 3! use Python 3!
**NOTE REGARDING DJANGO VERSION:**
The recommended release is Django 1.11. However, there initial support of
Django 2.0 as of version 0.2.7 if you'd like to try it out.
Please report any bugs you find!
You can quickly install the latest stable version of `django-helpdesk` You can quickly install the latest stable version of `django-helpdesk`
app via `pip`:: app via `pip`::

View File

@ -23,7 +23,8 @@ CLASSIFIERS = ['Development Status :: 4 - Beta',
'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.6',
'Framework :: Django'] 'Framework :: Django :: 1.11',
'Framework :: Django :: 2.0']
KEYWORDS = [] KEYWORDS = []
PACKAGES = ['demodesk'] PACKAGES = ['demodesk']
REQUIREMENTS = [ REQUIREMENTS = [

View File

@ -71,5 +71,5 @@ Dependencies
1. Python 3.4+ (or 2.7, but deprecated and support will be removed next release) 1. Python 3.4+ (or 2.7, but deprecated and support will be removed next release)
2. Django 1.11 or newer 2. Django 1.11 or newer
3. An existing **working** Django project with database etc. If you cannot log into the Admin, you won't get this product working! This means you **must** run `syncdb` **before** you add ``helpdesk`` to your ``INSTALLED_APPS``. 3. An existing **working** Django project with database etc. If you cannot log into the Admin, you won't get this product working! This means you **must** run `migrate` **before** you add ``helpdesk`` to your ``INSTALLED_APPS``.

View File

@ -105,6 +105,22 @@ These options only change display of items on public-facing pages, not staff pag
**Default:** ``HELPDESK_SUBMIT_A_TICKET_PUBLIC = True`` **Default:** ``HELPDESK_SUBMIT_A_TICKET_PUBLIC = True``
Options for public ticket submission form
-----------------------------------------
- **HELPDESK_PUBLIC_TICKET_QUEUE** Sets the queue for tickets submitted through the public form. If defined, the matching form field will be hidden. This cannot be `None` but must be set to a valid queue slug.
**Default:** Not defined
- **HELPDESK_PUBLIC_TICKET_PRIORITY** Sets the priority for tickets submitted through the public form. If defined, the matching form field will be hidden. Must be set to a valid integer priority.
**Default:** Not defined
- **HELPDESK_PUBLIC_TICKET_DUE_DATE** Sets the due date for tickets submitted through the public form. If defined, the matching form field will be hidden. Set to `None` if you want to hide the form field but do not want to define a value.
**Default:** Not defined
Options that change ticket updates Options that change ticket updates
---------------------------------- ----------------------------------
@ -149,9 +165,9 @@ Staff Ticket Creation Settings
Staff Ticket View Settings Staff Ticket View Settings
------------------------------ ------------------------------
- **HELPDESK_ENABLE_PER_QUEUE_PERMISSION** If ``True``, logged in staff users only see queues and tickets to which they have specifically been granted access - this holds for the dashboard, ticket query, and ticket report views. User assignment is done through the standard ``django.admin.admin`` permissions. *Note*: Staff with access to admin interface will be able to see the full list of tickets, but won't have access to details and could not modify them. This setting does not prevent staff users from creating tickets for all queues. Also, superuser accounts have full access to all queues, regardless of whatever queue memberships they have been granted. - **HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION** If ``True``, logged in staff users only see queues and tickets to which they have specifically been granted access - this holds for the dashboard, ticket query, and ticket report views. User assignment is done through the standard ``django.admin.admin`` permissions. *Note*: Staff with access to admin interface will be able to see the full list of tickets, but won't have access to details and could not modify them. This setting does not prevent staff users from creating tickets for all queues. Also, superuser accounts have full access to all queues, regardless of whatever queue memberships they have been granted.
**Default:** ``HELPDESK_ENABLE_PER_QUEUE_PERMISSION = False`` **Default:** ``HELPDESK_ENABLE_PER_QUEUE_STAFF_PERMISSION = False``

View File

@ -368,6 +368,14 @@ class PublicTicketForm(AbstractTicketForm):
Add any (non-staff) custom fields that are defined to the form Add any (non-staff) custom fields that are defined to the form
""" """
super(PublicTicketForm, self).__init__(*args, **kwargs) super(PublicTicketForm, self).__init__(*args, **kwargs)
if hasattr(settings, 'HELPDESK_PUBLIC_TICKET_QUEUE'):
self.fields['queue'].widget = forms.HiddenInput()
if hasattr(settings, 'HELPDESK_PUBLIC_TICKET_PRIORITY'):
self.fields['priority'].widget = forms.HiddenInput()
if hasattr(settings, 'HELPDESK_PUBLIC_TICKET_DUE_DATE'):
self.fields['due_date'].widget = forms.HiddenInput()
self._add_form_custom_fields(False) self._add_form_custom_fields(False)
def save(self): def save(self):

View File

@ -176,7 +176,14 @@ def process_queue(q, logger):
msgNum = msg.split(" ")[0] msgNum = msg.split(" ")[0]
logger.info("Processing message %s" % msgNum) logger.info("Processing message %s" % msgNum)
if six.PY2:
full_message = encoding.force_text("\n".join(server.retr(msgNum)[1]), errors='replace') full_message = encoding.force_text("\n".join(server.retr(msgNum)[1]), errors='replace')
else:
raw_content = server.retr(msgNum)[1]
if type(raw_content[0]) is bytes:
full_message = "\n".join([elm.decode('utf-8') for elm in raw_content])
else:
full_message = encoding.force_text("\n".join(raw_content), errors='replace')
ticket = ticket_from_message(message=full_message, queue=q, logger=logger) ticket = ticket_from_message(message=full_message, queue=q, logger=logger)
if ticket: if ticket:
@ -229,7 +236,10 @@ def process_queue(q, logger):
logger.info("Processing message %s" % num) logger.info("Processing message %s" % num)
status, data = server.fetch(num, '(RFC822)') status, data = server.fetch(num, '(RFC822)')
full_message = encoding.force_text(data[0][1], errors='replace') full_message = encoding.force_text(data[0][1], errors='replace')
try:
ticket = ticket_from_message(message=full_message, queue=q, logger=logger) ticket = ticket_from_message(message=full_message, queue=q, logger=logger)
except TypeError:
ticket = None # hotfix. Need to work out WHY.
if ticket: if ticket:
server.store(num, '+FLAGS', '\\Deleted') server.store(num, '+FLAGS', '\\Deleted')
logger.info("Successfully processed message %s, deleted from IMAP server" % num) logger.info("Successfully processed message %s, deleted from IMAP server" % num)
@ -389,8 +399,9 @@ def ticket_from_message(message, queue, logger):
if not body: if not body:
mail = BeautifulSoup(part.get_payload(), "lxml") mail = BeautifulSoup(part.get_payload(), "lxml")
if ">" in mail.text: if ">" in mail.text:
message_body = mail.text.split(">")[1] body = mail.find('body')
body = message_body.encode('ascii', errors='ignore') body = body.text
body = body.encode('ascii', errors='ignore')
else: else:
body = mail.text body = mail.text
@ -459,7 +470,7 @@ def ticket_from_message(message, queue, logger):
for ccemail in new_cc: for ccemail in new_cc:
tcc = TicketCC.objects.create( tcc = TicketCC.objects.create(
ticket=t, ticket=t,
email=ccemail, email=ccemail.replace('\n', ' ').replace('\r', ' '),
can_view=True, can_view=True,
can_update=False can_update=False
) )

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2018-01-19 09:48
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('helpdesk', '0016_alter_model_options'),
]
operations = [
migrations.AlterField(
model_name='queue',
name='default_owner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_owner', to=settings.AUTH_USER_MODEL, verbose_name='Default owner'),
),
]

View File

@ -14,6 +14,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.utils import six
from django.utils.translation import ugettext_lazy as _, ugettext from django.utils.translation import ugettext_lazy as _, ugettext
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
@ -1120,7 +1121,10 @@ class UserSettings(models.Model):
except ImportError: except ImportError:
import cPickle as pickle import cPickle as pickle
from helpdesk.lib import b64encode from helpdesk.lib import b64encode
if six.PY2:
self.settings_pickled = b64encode(pickle.dumps(data)) self.settings_pickled = b64encode(pickle.dumps(data))
else:
self.settings_pickled = b64encode(pickle.dumps(data)).decode()
def _get_settings(self): def _get_settings(self):
# return a python dictionary representing the pickled data. # return a python dictionary representing the pickled data.
@ -1130,13 +1134,10 @@ class UserSettings(models.Model):
import cPickle as pickle import cPickle as pickle
from helpdesk.lib import b64decode from helpdesk.lib import b64decode
try: try:
if six.PY3: if six.PY2:
if type(self.settings_pickled) is bytes:
return pickle.loads(b64decode(str(self.settings_pickled, 'utf8')))
else:
return pickle.loads(b64decode(bytes(self.settings_pickled, 'utf8')))
else:
return pickle.loads(b64decode(str(self.settings_pickled))) return pickle.loads(b64decode(str(self.settings_pickled)))
else:
return pickle.loads(b64decode(self.settings_pickled.encode('utf-8')))
except pickle.UnpicklingError: except pickle.UnpicklingError:
return {} return {}

View File

@ -8,7 +8,6 @@
* *
* http://api.jqueryui.com/category/theming/ * http://api.jqueryui.com/category/theming/
* *
* To view and modify this theme, visit http://jqueryui.com/themeroller/?bgShadowXPos=&bgOverlayXPos=&bgErrorXPos=&bgHighlightXPos=&bgContentXPos=&bgHeaderXPos=&bgActiveXPos=&bgHoverXPos=&bgDefaultXPos=&bgShadowYPos=&bgOverlayYPos=&bgErrorYPos=&bgHighlightYPos=&bgContentYPos=&bgHeaderYPos=&bgActiveYPos=&bgHoverYPos=&bgDefaultYPos=&bgShadowRepeat=&bgOverlayRepeat=&bgErrorRepeat=&bgHighlightRepeat=&bgContentRepeat=&bgHeaderRepeat=&bgActiveRepeat=&bgHoverRepeat=&bgDefaultRepeat=&iconsHover=url(%22images%2Fui-icons_555555_256x240.png%22)&iconsHighlight=url(%22images%2Fui-icons_777620_256x240.png%22)&iconsHeader=url(%22images%2Fui-icons_444444_256x240.png%22)&iconsError=url(%22images%2Fui-icons_cc0000_256x240.png%22)&iconsDefault=url(%22images%2Fui-icons_777777_256x240.png%22)&iconsContent=url(%22images%2Fui-icons_444444_256x240.png%22)&iconsActive=url(%22images%2Fui-icons_ffffff_256x240.png%22)&bgImgUrlShadow=&bgImgUrlOverlay=&bgImgUrlHover=&bgImgUrlHighlight=&bgImgUrlHeader=&bgImgUrlError=&bgImgUrlDefault=&bgImgUrlContent=&bgImgUrlActive=&opacityFilterShadow=Alpha(Opacity%3D30)&opacityFilterOverlay=Alpha(Opacity%3D30)&opacityShadowPerc=30&opacityOverlayPerc=30&iconColorHover=%23555555&iconColorHighlight=%23777620&iconColorHeader=%23444444&iconColorError=%23cc0000&iconColorDefault=%23777777&iconColorContent=%23444444&iconColorActive=%23ffffff&bgImgOpacityShadow=0&bgImgOpacityOverlay=0&bgImgOpacityError=95&bgImgOpacityHighlight=55&bgImgOpacityContent=75&bgImgOpacityHeader=75&bgImgOpacityActive=65&bgImgOpacityHover=75&bgImgOpacityDefault=75&bgTextureShadow=flat&bgTextureOverlay=flat&bgTextureError=flat&bgTextureHighlight=flat&bgTextureContent=flat&bgTextureHeader=flat&bgTextureActive=flat&bgTextureHover=flat&bgTextureDefault=flat&cornerRadius=3px&fwDefault=normal&ffDefault=Arial%2CHelvetica%2Csans-serif&fsDefault=1em&cornerRadiusShadow=8px&thicknessShadow=5px&offsetLeftShadow=0px&offsetTopShadow=0px&opacityShadow=.3&bgColorShadow=%23666666&opacityOverlay=.3&bgColorOverlay=%23aaaaaa&fcError=%235f3f3f&borderColorError=%23f1a899&bgColorError=%23fddfdf&fcHighlight=%23777620&borderColorHighlight=%23dad55e&bgColorHighlight=%23fffa90&fcContent=%23333333&borderColorContent=%23dddddd&bgColorContent=%23ffffff&fcHeader=%23333333&borderColorHeader=%23dddddd&bgColorHeader=%23e9e9e9&fcActive=%23ffffff&borderColorActive=%23003eff&bgColorActive=%23007fff&fcHover=%232b2b2b&borderColorHover=%23cccccc&bgColorHover=%23ededed&fcDefault=%23454545&borderColorDefault=%23c5c5c5&bgColorDefault=%23f6f6f6
*/ */

View File

@ -6,4 +6,4 @@
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'><b>{{ queue.title }}</b>{% if queue.email_address %}<br><a href='mailto:{{ queue.email_address }}'>{{ queue.email_address }}</a>{% endif %}</p> <p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 11pt;'><b>{{ queue.title }}</b>{% if queue.email_address %}<br><a href='mailto:{{ queue.email_address }}'>{{ queue.email_address }}</a>{% endif %}</p>
<p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 9pt; color: #808080;' color='#808080'>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.</p> <p style='font-family: "Trebuchet MS", Arial, sans-serif; font-size: 9pt; color: #808080;'>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.</p>

View File

@ -105,6 +105,9 @@ $(document).on('change', ':file', function() {
</li> </li>
{% if forloop.last %}</ul></div>{% endif %} {% if forloop.last %}</ul></div>{% endif %}
{% endfor %} {% endfor %}
<!--- ugly long test to suppress the following if it will be empty, to save vertical space -->
{% with possible=helpdesk_settings.HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP %}
{% if possible and followup.user and request.user == followup.user and not followup.ticketchange_set.all or possible and user.is_superuser and helpdesk_settings.HELPDESK_SHOW_DELETE_BUTTON_SUPERUSER_FOLLOW_UP %}
<hr> <hr>
<div class="btn-group"> <div class="btn-group">
{% if helpdesk_settings.HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP %} {% if helpdesk_settings.HELPDESK_SHOW_EDIT_BUTTON_FOLLOW_UP %}
@ -116,6 +119,7 @@ $(document).on('change', ':file', function() {
<a href="{% url 'helpdesk:followup_delete' ticket.id followup.id %}" class='followup-edit'><button type="button" class="btn btn-warning btn-xs"><i class="fa fa-trash"></i>&nbsp;{% trans "Delete" %}</button></a> <a href="{% url 'helpdesk:followup_delete' ticket.id followup.id %}" class='followup-edit'><button type="button" class="btn btn-warning btn-xs"><i class="fa fa-trash"></i>&nbsp;{% trans "Delete" %}</button></a>
{% endif %} {% endif %}
</div> </div>
{% endif %}{% endwith %}
</div> </div>
</div> </div>
</li> </li>

View File

@ -1,4 +1,5 @@
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
from django.core import mail from django.core import mail
from django.urls import reverse from django.urls import reverse
from django.test import TestCase from django.test import TestCase
@ -11,6 +12,7 @@ except ImportError: # python 2
from urlparse import urlparse from urlparse import urlparse
from helpdesk.templatetags.ticket_to_link import num_to_link from helpdesk.templatetags.ticket_to_link import num_to_link
from helpdesk.views.staff import _is_my_ticket
class TicketActionsTestCase(TestCase): class TicketActionsTestCase(TestCase):
@ -22,7 +24,8 @@ class TicketActionsTestCase(TestCase):
slug='q1', slug='q1',
allow_public_submission=True, allow_public_submission=True,
new_ticket_cc='new.public@example.com', new_ticket_cc='new.public@example.com',
updated_ticket_cc='update.public@example.com') updated_ticket_cc='update.public@example.com'
)
self.ticket_data = { self.ticket_data = {
'title': 'Test Ticket', 'title': 'Test Ticket',
@ -32,6 +35,7 @@ class TicketActionsTestCase(TestCase):
self.client = Client() self.client = Client()
def loginUser(self, is_staff=True): def loginUser(self, is_staff=True):
"""Create a staff user and login"""
User = get_user_model() User = get_user_model()
self.user = User.objects.create( self.user = User.objects.create(
username='User_1', username='User_1',
@ -124,6 +128,32 @@ class TicketActionsTestCase(TestCase):
response = self.client.post(reverse('helpdesk:update', kwargs={'ticket_id': ticket_id}), post_data, follow=True) response = self.client.post(reverse('helpdesk:update', kwargs={'ticket_id': ticket_id}), post_data, follow=True)
self.assertContains(response, 'Changed Status from Open to Closed') self.assertContains(response, 'Changed Status from Open to Closed')
def test_is_my_ticket(self):
"""Tests whether non-staff but assigned user still counts as owner"""
# make non-staff user
self.loginUser(is_staff=False)
# create second user
User = get_user_model()
self.user2 = User.objects.create(
username='User_2',
is_staff=False,
)
initial_data = {
'title': 'Private ticket test',
'queue': self.queue_public,
'assigned_to': self.user,
'status': Ticket.OPEN_STATUS,
}
# create ticket
ticket = Ticket.objects.create(**initial_data)
self.assertEqual(_is_my_ticket(self.user, ticket), True)
self.assertEqual(_is_my_ticket(self.user2, ticket), False)
def test_num_to_link(self): def test_num_to_link(self):
"""Test that we are correctly expanding links to tickets from IDs""" """Test that we are correctly expanding links to tickets from IDs"""

View File

@ -7,11 +7,17 @@ views/public.py - All public facing views, eg non-staff (no authentication
required) views. required) views.
""" """
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.urls import reverse try:
from django.http import HttpResponseRedirect # Django 2.0+
from django.urls import reverse
except ImportError:
# Django < 2
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import render from django.shortcuts import render
from django.utils.http import urlquote from django.utils.http import urlquote
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.conf import settings
from helpdesk import settings as helpdesk_settings from helpdesk import settings as helpdesk_settings
from helpdesk.decorators import protect_view, is_helpdesk_staff from helpdesk.decorators import protect_view, is_helpdesk_staff
@ -61,6 +67,19 @@ def homepage(request):
except Queue.DoesNotExist: except Queue.DoesNotExist:
queue = None queue = None
initial_data = {} initial_data = {}
# add pre-defined data for public ticket
if hasattr(settings, 'HELPDESK_PUBLIC_TICKET_QUEUE'):
# get the requested queue; return an error if queue not found
try:
queue = Queue.objects.get(slug=settings.HELPDESK_PUBLIC_TICKET_QUEUE)
except Queue.DoesNotExist:
return HttpResponse(status=500)
if hasattr(settings, 'HELPDESK_PUBLIC_TICKET_PRIORITY'):
initial_data['priority'] = settings.HELPDESK_PUBLIC_TICKET_PRIORITY
if hasattr(settings, 'HELPDESK_PUBLIC_TICKET_DUE_DATE'):
initial_data['due_date'] = settings.HELPDESK_PUBLIC_TICKET_DUE_DATE
if queue: if queue:
initial_data['queue'] = queue.id initial_data['queue'] = queue.id
@ -91,6 +110,8 @@ def view_ticket(request):
ticket = Ticket.objects.get(id=ticket_id, submitter_email__iexact=email) ticket = Ticket.objects.get(id=ticket_id, submitter_email__iexact=email)
except ObjectDoesNotExist: except ObjectDoesNotExist:
error_message = _('Invalid ticket ID or e-mail address. Please try again.') error_message = _('Invalid ticket ID or e-mail address. Please try again.')
except ValueError:
error_message = _('Invalid ticket ID or e-mail address. Please try again.')
else: else:
if is_helpdesk_staff(request.user): if is_helpdesk_staff(request.user):
redirect_url = reverse('helpdesk:view', args=[ticket_id]) redirect_url = reverse('helpdesk:view', args=[ticket_id])

View File

@ -92,7 +92,7 @@ def _has_access_to_queue(user, queue):
def _is_my_ticket(user, ticket): def _is_my_ticket(user, ticket):
"""Check to see if the user has permission to access """Check to see if the user has permission to access
a ticket. If not then deny access.""" a ticket. If not then deny access."""
if user.is_superuser or user.is_staff or user.id == ticket.customer_id: if user.is_superuser or user.is_staff or user.id == ticket.assigned_to.id:
return True return True
else: else:
return False return False

View File

@ -1,4 +1,4 @@
Django>=1.11,<2 Django>=1.11,<3
django-bootstrap-form>=3.3,<4 django-bootstrap-form>=3.3,<4
email-reply-parser email-reply-parser
django-markdown-deux django-markdown-deux

View File

@ -136,6 +136,7 @@ setup(
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.6",
"Framework :: Django", "Framework :: Django",
"Framework :: Django :: 1.11", "Framework :: Django :: 1.11",
"Framework :: Django :: 2.0",
"Environment :: Web Environment", "Environment :: Web Environment",
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Intended Audience :: Customer Service", "Intended Audience :: Customer Service",