diff --git a/example/__init__.py b/example/example/__init__.py similarity index 100% rename from example/__init__.py rename to example/example/__init__.py diff --git a/example/example/settings.py b/example/example/settings.py new file mode 100644 index 0000000..af0bdd1 --- /dev/null +++ b/example/example/settings.py @@ -0,0 +1,63 @@ +# +# A minimal settings file that ought to work out of the box for just about +# anyone trying this project. It's deliberately missing most settings to keep +# everything simple. +# +# A real app would have a lot more settings. The only important bit as far as +# django-FAQ is concerned is to have `faq` in INSTALLED_APPS. +# + +import os + +PROJECT_DIR = os.path.dirname(__file__) +DEBUG = True + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.normpath(os.path.join(PROJECT_DIR, 'faq.db')), + } +} + +SITE_ID = 1 +SECRET_KEY = 'c#zi(mv^n+4te_sy$hpb*zdo7#f7ccmp9ro84yz9bmmfqj9y*c' +ROOT_URLCONF = '%s.urls' % os.path.basename(PROJECT_DIR) +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [ + os.path.normpath(os.path.join(os.path.dirname(PROJECT_DIR), 'templates')), + ], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.contrib.auth.context_processors.auth', + 'django.template.context_processors.debug', + 'django.template.context_processors.i18n', + 'django.template.context_processors.media', + 'django.template.context_processors.static', + 'django.template.context_processors.tz', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.admin', + 'fack', +) + +MIDDLEWARE_CLASSES = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] diff --git a/example/urls.py b/example/example/urls.py similarity index 83% rename from example/urls.py rename to example/example/urls.py index 16b2e00..fcb36d5 100644 --- a/example/urls.py +++ b/example/example/urls.py @@ -1,9 +1,10 @@ -from django.conf.urls.defaults import patterns, url, include +from django.conf.urls import url, include from django.contrib import admin; admin.autodiscover() from django.conf import settings from django.views.generic import TemplateView +from django.views.static import serve -urlpatterns = patterns('', +urlpatterns = [ # Just a simple example "home" page to show a bit of help/info. url(r'^$', TemplateView.as_view(template_name="home.html")), @@ -15,7 +16,7 @@ # Normally we'd do this if DEBUG only, but this is just an example app. url(regex = r'^static/(?P.*)$', - view = 'django.views.static.serve', + view = serve, kwargs = {'document_root': settings.MEDIA_ROOT} ), -) \ No newline at end of file +] diff --git a/example/manage.py b/example/manage.py index 41e1821..56ca326 100755 --- a/example/manage.py +++ b/example/manage.py @@ -3,19 +3,21 @@ import os import sys -try: - import faq -except ImportError: - sys.stderr.write("django-fack isn't installed; trying to use a source checkout in ../fack.") - sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from django.core.management import execute_manager -try: - import settings # Assumed to be in the same directory. -except ImportError: - import sys - sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) - sys.exit(1) - if __name__ == "__main__": - execute_manager(settings) + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/example/settings.py b/example/settings.py deleted file mode 100644 index f74b299..0000000 --- a/example/settings.py +++ /dev/null @@ -1,35 +0,0 @@ -# -# A minimal settings file that ought to work out of the box for just about -# anyone trying this project. It's deliberately missing most settings to keep -# everything simple. -# -# A real app would have a lot more settings. The only important bit as far as -# django-FAQ is concerned is to have `faq` in INSTALLED_APPS. -# - -import os - -PROJECT_DIR = os.path.dirname(__file__) -DEBUG = TEMPLATE_DEBUG = True - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(PROJECT_DIR, 'faq.db'), - } -} - -SITE_ID = 1 -SECRET_KEY = 'c#zi(mv^n+4te_sy$hpb*zdo7#f7ccmp9ro84yz9bmmfqj9y*c' -ROOT_URLCONF = 'urls' -TEMPLATE_DIRS = ( - [os.path.join(PROJECT_DIR, "templates")] -) -INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.admin', - 'fack', -) \ No newline at end of file diff --git a/example/templates/home.html b/example/templates/home.html index aec8ba0..dba9b39 100644 --- a/example/templates/home.html +++ b/example/templates/home.html @@ -1,7 +1,6 @@ {% extends "faq/base.html" %} {% block body %} -{% load url from future %}

Welcome to the django-fack example project

If you aren't seeing any data below, or if you're getting random 404s, make @@ -40,7 +39,7 @@

Welcome to the django-fack example project

- Speaking of the admin, here's the FAQ admin. + Speaking of the admin, here's the FAQ admin.
Remember that you can clean up submitted questions in the admin before the diff --git a/fack/_testrunner.py b/fack/_testrunner.py index b0faf74..2e20017 100644 --- a/fack/_testrunner.py +++ b/fack/_testrunner.py @@ -2,20 +2,56 @@ Test support harness to make setup.py test work. """ -import sys +import sys, os +import django from django.conf import settings settings.configure( DATABASES = { 'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory;'} }, - INSTALLED_APPS = ['django.contrib.auth', 'django.contrib.contenttypes', 'fack'], + INSTALLED_APPS = [ + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.messages', + 'django.contrib.sessions', + 'fack', + ], + MIDDLEWARE_CLASSES = [ + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + ], ROOT_URLCONF = 'fack.urls', + TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [ + os.path.normpath(os.path.join(os.path.dirname(__file__), 'templates')), + ], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.contrib.auth.context_processors.auth', + 'django.template.context_processors.debug', + 'django.template.context_processors.i18n', + 'django.template.context_processors.media', + 'django.template.context_processors.static', + 'django.template.context_processors.tz', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, + ] ) +if hasattr(django, 'setup'): + django.setup() def runtests(): import django.test.utils runner_class = django.test.utils.get_runner(settings) test_runner = runner_class(verbosity=1, interactive=True) failures = test_runner.run_tests(['fack']) - sys.exit(failures) \ No newline at end of file + sys.exit(failures) diff --git a/fack/faq b/fack/faq deleted file mode 120000 index 23e9387..0000000 --- a/fack/faq +++ /dev/null @@ -1 +0,0 @@ -faq \ No newline at end of file diff --git a/fack/managers.py b/fack/managers.py index 2cea25c..bf42e22 100644 --- a/fack/managers.py +++ b/fack/managers.py @@ -13,4 +13,4 @@ def get_query_set(self): return QuestionQuerySet(self.model) def active(self): - return self.get_query_set().active() \ No newline at end of file + return self.get_query_set().active() diff --git a/fack/models.py b/fack/models.py index dfd8d77..b859252 100644 --- a/fack/models.py +++ b/fack/models.py @@ -2,13 +2,17 @@ import datetime +import django from django.conf import settings from django.db import models from django.utils.translation import ugettext_lazy as _ from django.template.defaultfilters import slugify from django.utils.encoding import python_2_unicode_compatible -from .managers import QuestionManager +from .managers import QuestionManager, QuestionQuerySet + + +AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') @python_2_unicode_compatible @@ -63,12 +67,15 @@ class Question(models.Model): created_on = models.DateTimeField(_('created on'), default=datetime.datetime.now) updated_on = models.DateTimeField(_('updated on')) - created_by = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('created by'), + created_by = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_('created by'), null=True, related_name="+") - updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('updated by'), + updated_by = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_('updated by'), null=True, related_name="+") - objects = QuestionManager() + if django.VERSION >= (1, 7): + objects = QuestionQuerySet.as_manager() + else: + objects = QuestionManager() class Meta: verbose_name = _("Frequent asked question") diff --git a/fack/tests/test_admin.py b/fack/tests/test_admin.py index 994000f..52e2a6b 100644 --- a/fack/tests/test_admin.py +++ b/fack/tests/test_admin.py @@ -11,7 +11,10 @@ import mock from django.contrib import admin from django.contrib.auth.models import User -from django.utils import unittest +try: + from django.utils import unittest +except ImportError: # Django >= 1.9 + import unittest from django.http import HttpRequest from django import forms from ..admin import QuestionAdmin @@ -31,7 +34,7 @@ def test_question_admin_save_model(self): # Test saving a new model. req.user = user1 qa.save_model(req, obj, form, change=False) - obj.save.assert_called() + self.assertEqual(obj.save.call_count, 1) self.assertEqual(obj.created_by, user1, "created_by wasn't set to request.user") self.assertEqual(obj.updated_by, user1, "updated_by wasn't set to request.user") @@ -39,6 +42,6 @@ def test_question_admin_save_model(self): obj.save.reset_mock() req.user = user2 qa.save_model(req, obj, form, change=True) - obj.save.assert_called() + self.assertEqual(obj.save.call_count, 1) self.assertEqual(obj.created_by, user1, "created_by shouldn't have been changed") self.assertEqual(obj.updated_by, user2, "updated_by wasn't set to request.user") \ No newline at end of file diff --git a/fack/tests/test_templatetags.py b/fack/tests/test_templatetags.py index 02f3d35..fc71131 100644 --- a/fack/tests/test_templatetags.py +++ b/fack/tests/test_templatetags.py @@ -2,7 +2,10 @@ import django.test from django import template -from django.utils import unittest +try: + from django.utils import unittest +except ImportError: # Django >= 1.9 + import unittest from ..templatetags import faqtags from ..models import Topic @@ -20,7 +23,7 @@ def compile(self, tagfunc, token_contents): Assumes the tag doesn't use the parser, so this won't work for block tags. """ - t = template.Token(template.TOKEN_BLOCK, token_contents) + t = template.base.Token(template.base.TOKEN_BLOCK, token_contents) return tagfunc(None, t) def test_faqs_for_topic_compile(self): diff --git a/fack/tests/test_views.py b/fack/tests/test_views.py index 11b2d53..9a83a97 100644 --- a/fack/tests/test_views.py +++ b/fack/tests/test_views.py @@ -10,21 +10,13 @@ class FAQViewTests(django.test.TestCase): urls = 'fack.urls' fixtures = ['faq_test_data.json'] - - def setUp(self): - # Make some test templates available. - self._oldtd = settings.TEMPLATE_DIRS - settings.TEMPLATE_DIRS = [os.path.join(os.path.dirname(__file__), 'templates')] - - def tearDown(self): - settings.TEMPLATE_DIRS = self._oldtd - + def test_submit_faq_get(self): response = self.client.get('/submit/') self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "faq/submit_question.html") - @mock.patch('django.contrib.messages') + @mock.patch('fack.views.messages') def test_submit_faq_post(self, mock_messages): data = { 'topic': '1', @@ -32,7 +24,7 @@ def test_submit_faq_post(self, mock_messages): 'answer': 'Blue. I mean red. I mean *AAAAHHHHH....*', } response = self.client.post('/submit/', data) - mock_messages.sucess.assert_called() + self.assertEqual(mock_messages.success.call_count, 1) self.assertRedirects(response, "/submit/thanks/") self.assert_( Question.objects.filter(text=data['text']).exists(), diff --git a/fack/urls.py b/fack/urls.py index 39dae9b..1212c18 100644 --- a/fack/urls.py +++ b/fack/urls.py @@ -3,10 +3,10 @@ try: from django.conf.urls.defaults import * except: - from django.conf.urls import url, patterns + from django.conf.urls import url from . import views -urlpatterns = patterns('', +urlpatterns = [ url(regex = r'^$', view = views.TopicList.as_view(), name = 'faq_topic_list', @@ -27,4 +27,4 @@ view = views.QuestionDetail.as_view(), name = 'faq_question_detail', ), -) +] diff --git a/tox.ini b/tox.ini index 793ce1e..11bf405 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,16 @@ [tox] downloadcache = .tox/_download/ -envlist = py25, py26, py27, docs +envlist = py27-dj{14,15,16,17,18,19}, docs [testenv] deps = mock + dj14: Django>=1.4,<1.5 + dj15: Django>=1.5,<1.6 + dj16: Django>=1.6,<1.7 + dj17: Django>=1.7,<1.8 + dj18: Django>=1.8,<1.9 + dj19: Django>=1.9,<1.10 commands = {envpython} setup.py test @@ -23,4 +29,4 @@ commands = basepython = python deps = sphinx commands = - {envpython} setup.py build_sphinx \ No newline at end of file + {envpython} setup.py build_sphinx