From 2e5a75694fd658c48767809f1b3f106332d588d5 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Mon, 20 Aug 2012 14:32:32 -0500 Subject: [PATCH 01/21] Update faq/urls/shallow.py Made the shallow urls follow the /faq/#topic and /faq/#question model --- faq/urls/shallow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/faq/urls/shallow.py b/faq/urls/shallow.py index a70f2a6..c33a275 100644 --- a/faq/urls/shallow.py +++ b/faq/urls/shallow.py @@ -14,7 +14,7 @@ urlpatterns = patterns('', url(r'^$', topic_list, name='faq-topic-list'), - url(r'^(?P[-\w]+)/$', topic_detail, name='faq-topic-detail'), - url(r'^(?P[-\w]+)/(?P[-\w]+)/$', question_detail, + url(r'^#(?P[-\w]+)$', topic_list, name='faq-topic-detail'), + url(r'^#(?P[-\w]+)$', topic_list, name='faq-question-detail'), ) From 26a8394f36cff2fd836b3268c07ad12ffb287710 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Mon, 4 Feb 2013 07:11:42 -0600 Subject: [PATCH 02/21] Version 0.8.4: Made registering search indexes optional via setting --- faq/__init__.py | 2 +- faq/search_indexes.py | 15 ++++++++------- faq/settings.py | 1 + 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/faq/__init__.py b/faq/__init__.py index 9749719..31be8f8 100644 --- a/faq/__init__.py +++ b/faq/__init__.py @@ -18,7 +18,7 @@ from django.utils.translation import ugettext_lazy as _ -__version__ = '0.8.3' +__version__ = '0.8.4' # Mark the app_label for translation. _(u'faq') diff --git a/faq/search_indexes.py b/faq/search_indexes.py index ebc93c6..4f81fd4 100644 --- a/faq/search_indexes.py +++ b/faq/search_indexes.py @@ -12,7 +12,7 @@ from haystack import indexes -from faq.settings import SEARCH_INDEX +from faq.settings import SEARCH_INDEX, REGISTER_SEARCH from faq.models import Topic, Question @@ -61,9 +61,10 @@ def get_queryset(self): # try/except in order to register search indexes with site for Haystack 1.X # without throwing exceptions with Haystack 2.0. -try: - from haystack.sites import site - site.register(Topic, TopicIndex) - site.register(Question, QuestionIndex) -except ImportError: - pass +if REGISTER_SEARCH: + try: + from haystack.sites import site + site.register(Topic, TopicIndex) + site.register(Question, QuestionIndex) + except ImportError: + pass diff --git a/faq/settings.py b/faq/settings.py index f37fc0d..cd308de 100644 --- a/faq/settings.py +++ b/faq/settings.py @@ -19,6 +19,7 @@ ) STATUS_CHOICES = getattr(settings, 'FAQ_STATUS_CHOICES', STATUS_CHOICES) +REGISTER_SEARCH = getattr(settings, 'FAQ_REGISTER_SEARCH', True) # Haystack settings. # The default search index used for the app is the default haystack index. From 0beabb226859753b56006b0bbc18aee0352d98e2 Mon Sep 17 00:00:00 2001 From: teo Date: Wed, 24 Jul 2013 20:36:32 -0300 Subject: [PATCH 03/21] Replacing old generic views with class based ones. Making {% url tags 1.5 compatible --- .gitignore | 1 + faq/templates/faq/question_detail.html | 2 +- faq/templates/faq/topic_detail.html | 2 +- faq/urls/deep.py | 20 ++++++++++------- faq/urls/normal.py | 11 ++++----- faq/urls/shallow.py | 8 +++---- faq/views/deep.py | 30 +++++++++---------------- faq/views/normal.py | 31 +++++++++----------------- faq/views/shallow.py | 20 +++++------------ 9 files changed, 53 insertions(+), 72 deletions(-) diff --git a/.gitignore b/.gitignore index 4ff5976..24a6676 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ build/ dist/ docs/_build MANIFEST +*.egg-info \ No newline at end of file diff --git a/faq/templates/faq/question_detail.html b/faq/templates/faq/question_detail.html index 8f653cf..a0f312b 100644 --- a/faq/templates/faq/question_detail.html +++ b/faq/templates/faq/question_detail.html @@ -9,7 +9,7 @@ {% block header %} - {% trans "FAQ" %} › + {% trans "FAQ" %}{{ topic }} › {{ question }} {% endblock %} diff --git a/faq/templates/faq/topic_detail.html b/faq/templates/faq/topic_detail.html index 53dca0b..4cc564e 100644 --- a/faq/templates/faq/topic_detail.html +++ b/faq/templates/faq/topic_detail.html @@ -13,7 +13,7 @@ {% block header %} - {% trans "FAQ" %} › {{ topic }} + {% trans "FAQ" %} › {{ topic }} {% endblock %} diff --git a/faq/urls/deep.py b/faq/urls/deep.py index e5b111a..aae99a2 100644 --- a/faq/urls/deep.py +++ b/faq/urls/deep.py @@ -2,9 +2,9 @@ from django.conf.urls.defaults import * -from faq.views.shallow import topic_list -from faq.views.normal import topic_detail -from faq.views.deep import question_detail +from faq.views.shallow import TopicListView +from faq.views.normal import TopicDetailView +from faq.views.deep import QuestionDetailView # Include these patterns if you want URLs like: @@ -14,9 +14,13 @@ # /faq/topic/question/ # -urlpatterns = patterns('', - url(r'^$', topic_list, name='faq-topic-list'), - url(r'^(?P[-\w]+)/$', topic_detail, name='faq-topic-detail'), - url(r'^(?P[-\w]+)/(?P[-\w]+)/$', question_detail, - name='faq-question-detail'), +urlpatterns = patterns( + '', + url(r'^$', TopicListView.as_view(), name='faq-topic-list'), + url(r'^(?P[-\w]+)/$', TopicDetailView.as_view(), name='faq-topic-detail'), + url( + r'^(?P[-\w]+)/(?P[-\w]+)/$', + QuestionDetailView.as_view(), + name='faq-question-detail' + ), ) diff --git a/faq/urls/normal.py b/faq/urls/normal.py index 49e39cc..2833f01 100644 --- a/faq/urls/normal.py +++ b/faq/urls/normal.py @@ -2,8 +2,8 @@ from django.conf.urls.defaults import * -from faq.views.shallow import topic_list -from faq.views.normal import topic_detail, question_detail +from faq.views.shallow import TopicListView +from faq.views.normal import TopicDetailView, question_detail # Include these patterns if you want URLs like: @@ -13,9 +13,10 @@ # /faq/topic/#question # -urlpatterns = patterns('', - url(r'^$', topic_list, name='faq-topic-list'), - url(r'^(?P[-\w]+)/$', topic_detail, name='faq-topic-detail'), +urlpatterns = patterns( + '', + url(r'^$', TopicListView.as_view(), name='faq-topic-list'), + url(r'^(?P[-\w]+)/$', TopicDetailView.as_view(), name='faq-topic-detail'), url(r'^(?P[-\w]+)/(?P[-\w]+)/$', question_detail, name='faq-question-detail'), ) diff --git a/faq/urls/shallow.py b/faq/urls/shallow.py index a70f2a6..a97ee7e 100644 --- a/faq/urls/shallow.py +++ b/faq/urls/shallow.py @@ -2,8 +2,7 @@ from django.conf.urls.defaults import * -from faq.views.shallow import topic_list, topic_detail, question_detail - +from faq.views.shallow import TopicListView, topic_detail, question_detail # Include these patterns if you want URLs like: # @@ -12,8 +11,9 @@ # /faq/#question # -urlpatterns = patterns('', - url(r'^$', topic_list, name='faq-topic-list'), +urlpatterns = patterns( + '', + url(r'^$', TopicListView.as_view(), name='faq-topic-list'), url(r'^(?P[-\w]+)/$', topic_detail, name='faq-topic-detail'), url(r'^(?P[-\w]+)/(?P[-\w]+)/$', question_detail, name='faq-question-detail'), diff --git a/faq/views/deep.py b/faq/views/deep.py index 55b273b..77d2a76 100644 --- a/faq/views/deep.py +++ b/faq/views/deep.py @@ -1,27 +1,19 @@ # -*- coding: utf-8 -*- -from django.views.generic.list_detail import object_detail - from faq.models import Topic, Question +from django.views.generic.detail import DetailView -def question_detail(request, topic_slug, slug): - """ - A detail view of a Question. - Templates: - :template:`faq/question_detail.html` - Context: - question - A :model:`faq.Question`. - topic - The :model:`faq.Topic` object related to ``question``. +class QuestionDetailView(DetailView): + model = Question - """ - extra_context = { - 'topic': Topic.objects.published().get(slug=topic_slug), - } + def get_context_data(self, **kwargs): + context = super(QuestionDetailView, self).get_context_data(**kwargs) + context['topic'] = Topic.objects.published().get( + slug=context['question'].topic.slug + ) + return context - return object_detail(request, queryset=Question.objects.published(), - extra_context=extra_context, template_object_name='question', - slug=slug) + def get_template_names(self): + return ['faq/question_detail.html'] diff --git a/faq/views/normal.py b/faq/views/normal.py index ad1004a..ed6995c 100644 --- a/faq/views/normal.py +++ b/faq/views/normal.py @@ -1,33 +1,24 @@ # -*- coding: utf-8 -*- from django.core.urlresolvers import reverse -from django.shortcuts import get_object_or_404, redirect -from django.views.generic.list_detail import object_detail +from django.views.generic.detail import DetailView from faq.models import Topic, Question from faq.views.shallow import _fragmentify -def topic_detail(request, slug): - """ - A detail view of a Topic - - Templates: - :template:`faq/topic_detail.html` - Context: - topic - An :model:`faq.Topic` object. - question_list - A list of all published :model:`faq.Question` objects that relate - to the given :model:`faq.Topic`. +class TopicDetailView(DetailView): + model = Topic - """ - extra_context = { - 'question_list': Question.objects.published().filter(topic__slug=slug), - } + def get_context_data(self, **kwargs): + context = super(TopicDetailView, self).get_context_data(**kwargs) + context['question_list'] = Question.objects.published().filter( + topic__slug=context['topic'].slug + ) + return context - return object_detail(request, queryset=Topic.objects.published(), - extra_context=extra_context, template_object_name='topic', slug=slug) + def get_template_names(self): + return ['faq/topic_detail.html'] def question_detail(request, topic_slug, slug): diff --git a/faq/views/shallow.py b/faq/views/shallow.py index 1a50852..2a13e15 100644 --- a/faq/views/shallow.py +++ b/faq/views/shallow.py @@ -2,7 +2,8 @@ from django.core.urlresolvers import reverse from django.shortcuts import get_object_or_404, redirect -from django.views.generic.list_detail import object_list + +from django.views.generic.list import ListView from faq.models import Topic, Question @@ -15,20 +16,11 @@ def _fragmentify(model, slug, url=None): return redirect(url + fragment, permanent=True) -def topic_list(request): - """ - A list view of all published Topics - - Templates: - :template:`faq/topic_list.html` - Context: - topic_list - A list of all published :model:`faq.Topic` objects that - relate to the current :model:`sites.Site`. +class TopicListView(ListView): + model = Topic - """ - return object_list(request, queryset=Topic.objects.published(), - template_object_name='topic') + def get_template_names(self): + return ['faq/topic_list.html'] def topic_detail(request, slug): From 1f04a78598ae3b0abb67ceb90974a171acb5eeda Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 26 Dec 2013 08:27:47 -0500 Subject: [PATCH 04/21] Version bump to 0.8.5 --- README.rst | 8 ++++---- faq/__init__.py | 2 +- setup.py | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index fa8391a..c6a94b1 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ -============================================================ - Frequently Asked Question (FAQ) management for Django apps -============================================================ +========================================================== +Frequently Asked Question (FAQ) management for Django apps +========================================================== This Django_ application provides the ability to create and manage lists of Frequently Asked Questions (FAQ), organized by topic. @@ -12,7 +12,7 @@ months. .. _Django: http://www.djangoproject.com/ -TODO’s +TODO's ------ Below are tasks that need done, features under consideration, and some diff --git a/faq/__init__.py b/faq/__init__.py index 31be8f8..c5d01e2 100644 --- a/faq/__init__.py +++ b/faq/__init__.py @@ -18,7 +18,7 @@ from django.utils.translation import ugettext_lazy as _ -__version__ = '0.8.4' +__version__ = '0.8.5' # Mark the app_label for translation. _(u'faq') diff --git a/setup.py b/setup.py index 73b859e..4920139 100644 --- a/setup.py +++ b/setup.py @@ -29,14 +29,14 @@ def get_version(): author_email='ben@benspaulding.us', license='BSD', download_url='http://github.com/benspaulding/django-faq/tarball/v%s' % get_version(), - long_description = get_long_desc(), - packages = [ + long_description=get_long_desc(), + packages=[ 'faq', 'faq.tests', 'faq.urls', 'faq.views', ], - package_data = { + package_data={ 'faq': [ 'fixtures/*', 'locale/*/LC_MESSAGES/*', From 8932d30c8f74a457362b29f6e945cd8e1935c1aa Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Mon, 30 Dec 2013 08:44:01 -0600 Subject: [PATCH 05/21] 0.8.6: Django 1.5 compatibility --- faq/__init__.py | 2 +- faq/admin.py | 6 ++---- faq/models.py | 32 +++++++++++++++++++++++--------- faq/urls/deep.py | 10 ++++++---- faq/urls/normal.py | 11 ++++++++--- faq/urls/shallow.py | 13 ++++++------- 6 files changed, 46 insertions(+), 28 deletions(-) diff --git a/faq/__init__.py b/faq/__init__.py index c5d01e2..e806b11 100644 --- a/faq/__init__.py +++ b/faq/__init__.py @@ -18,7 +18,7 @@ from django.utils.translation import ugettext_lazy as _ -__version__ = '0.8.5' +__version__ = '0.8.6' # Mark the app_label for translation. _(u'faq') diff --git a/faq/admin.py b/faq/admin.py index 9171e04..0716c46 100644 --- a/faq/admin.py +++ b/faq/admin.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from django.contrib import admin -from django.contrib.sites.models import Site from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_noop, ungettext @@ -27,7 +26,7 @@ def update_status(modeladmin, request, queryset, status): # Now log what happened. # Use ugettext_noop() 'cause this is going straight into the db. log_message = ugettext_noop(u'Changed status to \'%s\'.' % - obj.get_status_display()) + obj.get_status_display()) modeladmin.log_change(request, obj, log_message) # Send a message to the user telling them what has happened. @@ -105,8 +104,7 @@ def question_count(self, obj): class QuestionAdmin(FAQAdminBase): fieldsets = ( (None, { - 'fields': ('topic', 'question', 'slug', 'answer', 'status', - 'ordering')}), + 'fields': ('topic', 'question', 'slug', 'answer', 'status', 'ordering')}), ) list_display = ('question', 'topic', 'status', 'ordering') list_filter = ('status', 'topic', 'modified', 'created') diff --git a/faq/models.py b/faq/models.py index 589573d..d0cb9dd 100644 --- a/faq/models.py +++ b/faq/models.py @@ -75,10 +75,14 @@ class FAQBase(models.Model): created = models.DateTimeField(_(u'date created'), auto_now_add=True) modified = models.DateTimeField(_(u'date modified'), auto_now=True) - status = models.IntegerField(_(u'status'), choices=STATUS_CHOICES, + status = models.IntegerField( + _(u'status'), + choices=STATUS_CHOICES, # TODO: Genericize/fix the help_text. - db_index=True, default=DRAFTED, help_text=_(u'Only objects with \ - "published" status will be displayed publicly.')) + db_index=True, + default=DRAFTED, + help_text=_(u'Only objects with "published" ' + 'status will be displayed publicly.')) objects = OnSiteManager() @@ -93,9 +97,13 @@ class Topic(FAQBase): title = models.CharField(_(u'title'), max_length=255) slug = models.SlugField(_(u'slug'), unique=True, help_text=_(u'Used in \ the URL for the topic. Must be unique.')) - description = models.TextField(_(u'description'), blank=True, + description = models.TextField( + _(u'description'), + blank=True, help_text=_(u'A short description of this topic.')) - sites = models.ManyToManyField(Site, verbose_name=_(u'sites'), + sites = models.ManyToManyField( + Site, + verbose_name=_(u'sites'), related_name='faq_topics') class Meta(FAQBase.Meta): @@ -118,10 +126,15 @@ class Question(FAQBase): slug = models.SlugField(_(u'slug'), unique=True, help_text=_(u'Used in \ the URL for the Question. Must be unique.')) answer = models.TextField(_(u'answer')) - topic = models.ForeignKey(Topic, verbose_name=_(u'topic'), + topic = models.ForeignKey( + Topic, + verbose_name=_(u'topic'), related_name='questions') - ordering = models.PositiveSmallIntegerField(_(u'ordering'), blank=True, - db_index=True, help_text=_(u'An integer used to order the question \ + ordering = models.PositiveSmallIntegerField( + _(u'ordering'), + blank=True, + db_index=True, + help_text=_(u'An integer used to order the question \ amongst others related to the same topic. If not given this \ question will be last in the list.')) @@ -158,5 +171,6 @@ def save(self, *args, **kwargs): @models.permalink def get_absolute_url(self): - return ('faq-question-detail', (), {'topic_slug': self.topic.slug, + return ('faq-question-detail', (), { + 'topic_slug': self.topic.slug, 'slug': self.slug}) diff --git a/faq/urls/deep.py b/faq/urls/deep.py index aae99a2..ef0ce38 100644 --- a/faq/urls/deep.py +++ b/faq/urls/deep.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from django.conf.urls.defaults import * +from django.conf.urls import patterns, url from faq.views.shallow import TopicListView from faq.views.normal import TopicDetailView @@ -17,10 +17,12 @@ urlpatterns = patterns( '', url(r'^$', TopicListView.as_view(), name='faq-topic-list'), - url(r'^(?P[-\w]+)/$', TopicDetailView.as_view(), name='faq-topic-detail'), + url( + r'^(?P[-\w]+)/$', + TopicDetailView.as_view(), + name='faq-topic-detail'), url( r'^(?P[-\w]+)/(?P[-\w]+)/$', QuestionDetailView.as_view(), - name='faq-question-detail' - ), + name='faq-question-detail'), ) diff --git a/faq/urls/normal.py b/faq/urls/normal.py index 2833f01..c221f73 100644 --- a/faq/urls/normal.py +++ b/faq/urls/normal.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from django.conf.urls.defaults import * +from django.conf.urls import patterns, url from faq.views.shallow import TopicListView from faq.views.normal import TopicDetailView, question_detail @@ -16,7 +16,12 @@ urlpatterns = patterns( '', url(r'^$', TopicListView.as_view(), name='faq-topic-list'), - url(r'^(?P[-\w]+)/$', TopicDetailView.as_view(), name='faq-topic-detail'), - url(r'^(?P[-\w]+)/(?P[-\w]+)/$', question_detail, + url( + r'^(?P[-\w]+)/$', + TopicDetailView.as_view(), + name='faq-topic-detail'), + url( + r'^(?P[-\w]+)/(?P[-\w]+)/$', + question_detail, name='faq-question-detail'), ) diff --git a/faq/urls/shallow.py b/faq/urls/shallow.py index c19c8c7..06a9942 100644 --- a/faq/urls/shallow.py +++ b/faq/urls/shallow.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -from django.conf.urls.defaults import * - +from django.conf.urls import patterns, url from faq.views.shallow import TopicListView, topic_detail, question_detail # Include these patterns if you want URLs like: @@ -11,9 +10,9 @@ # /faq/#question # -urlpatterns = patterns('', - url(r'^$', topic_list, name='faq-topic-list'), - url(r'^#(?P[-\w]+)$', topic_list, name='faq-topic-detail'), - url(r'^#(?P[-\w]+)$', topic_list, - name='faq-question-detail'), +urlpatterns = patterns( + '', + url(r'^$', TopicListView.as_view(), name='faq-topic-list'), + url(r'^#(?P[-\w]+)$', topic_detail, name='faq-topic-detail'), + url(r'^#(?P[-\w]+)$', question_detail, name='faq-question-detail'), ) From 0f711aab3f92e052835c348f4351266485d1ea7e Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 23 Apr 2015 07:12:45 -0500 Subject: [PATCH 06/21] Initial Django 1.7 migrations --- faq/__init__.py | 2 +- faq/migrations/0001_initial.py | 62 ++++++++++++++++++++++++++++++++++ faq/migrations/__init__.py | 0 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 faq/migrations/0001_initial.py create mode 100644 faq/migrations/__init__.py diff --git a/faq/__init__.py b/faq/__init__.py index e806b11..10927bd 100644 --- a/faq/__init__.py +++ b/faq/__init__.py @@ -18,7 +18,7 @@ from django.utils.translation import ugettext_lazy as _ -__version__ = '0.8.6' +__version__ = '0.9b1' # Mark the app_label for translation. _(u'faq') diff --git a/faq/migrations/0001_initial.py b/faq/migrations/0001_initial.py new file mode 100644 index 0000000..44bd2fb --- /dev/null +++ b/faq/migrations/0001_initial.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('sites', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Question', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='date created')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='date modified')), + ('status', models.IntegerField(default=1, help_text='Only objects with "published" status will be displayed publicly.', db_index=True, verbose_name='status', choices=[(1, 'drafted'), (2, 'published'), (3, 'removed')])), + ('question', models.CharField(max_length=255, verbose_name='question')), + ('slug', models.SlugField(help_text='Used in the URL for the Question. Must be unique.', unique=True, verbose_name='slug')), + ('answer', models.TextField(verbose_name='answer')), + ('ordering', models.PositiveSmallIntegerField(help_text='An integer used to order the question amongst others related to the same topic. If not given this question will be last in the list.', db_index=True, verbose_name='ordering', blank=True)), + ], + options={ + 'ordering': ('ordering', 'question', 'slug'), + 'abstract': False, + 'get_latest_by': 'modified', + 'verbose_name': 'question', + 'verbose_name_plural': 'questions', + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='Topic', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='date created')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='date modified')), + ('status', models.IntegerField(default=1, help_text='Only objects with "published" status will be displayed publicly.', db_index=True, verbose_name='status', choices=[(1, 'drafted'), (2, 'published'), (3, 'removed')])), + ('title', models.CharField(max_length=255, verbose_name='title')), + ('slug', models.SlugField(help_text='Used in the URL for the topic. Must be unique.', unique=True, verbose_name='slug')), + ('description', models.TextField(help_text='A short description of this topic.', verbose_name='description', blank=True)), + ('sites', models.ManyToManyField(related_name='faq_topics', verbose_name='sites', to='sites.Site')), + ], + options={ + 'ordering': ('title', 'slug'), + 'abstract': False, + 'get_latest_by': 'modified', + 'verbose_name': 'topic', + 'verbose_name_plural': 'topics', + }, + bases=(models.Model,), + ), + migrations.AddField( + model_name='question', + name='topic', + field=models.ForeignKey(related_name='questions', verbose_name='topic', to='faq.Topic'), + preserve_default=True, + ), + ] diff --git a/faq/migrations/__init__.py b/faq/migrations/__init__.py new file mode 100644 index 0000000..e69de29 From 8d927a80ad5ce26596000ab49bed2138e6814289 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Wed, 24 Feb 2016 08:09:50 -0600 Subject: [PATCH 07/21] Updates get_query_set to get_queryset for Django 1.8 --- faq/__init__.py | 2 +- faq/models.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/faq/__init__.py b/faq/__init__.py index 10927bd..ae782f3 100644 --- a/faq/__init__.py +++ b/faq/__init__.py @@ -18,7 +18,7 @@ from django.utils.translation import ugettext_lazy as _ -__version__ = '0.9b1' +__version__ = '0.9b2' # Mark the app_label for translation. _(u'faq') diff --git a/faq/models.py b/faq/models.py index d0cb9dd..8d6c4c2 100644 --- a/faq/models.py +++ b/faq/models.py @@ -50,21 +50,21 @@ class OnSiteManager(models.Manager): def on_site(self): """Returns only items related to the current site.""" - return self.get_query_set().filter(**_field_lookups(self.model)) + return self.get_queryset().filter(**_field_lookups(self.model)) def drafted(self): """Returns only on-site items with a status of 'drafted'.""" - return self.get_query_set().filter( + return self.get_queryset().filter( **_field_lookups(self.model, DRAFTED)) def published(self): """Returns only on-site items with a status of 'published'.""" - return self.get_query_set().filter( + return self.get_queryset().filter( **_field_lookups(self.model, PUBLISHED)) def removed(self): """Returns only on-site items with a status of 'removed'.""" - return self.get_query_set().filter( + return self.get_queryset().filter( **_field_lookups(self.model, REMOVED)) From 6c4f8b76fdf72e429f91701162d155371f36a5a7 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Wed, 24 Feb 2016 08:11:44 -0600 Subject: [PATCH 08/21] Updating setup.py for latest setuptools --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4920139..2159a14 100644 --- a/setup.py +++ b/setup.py @@ -2,14 +2,16 @@ import os -from distutils.core import setup +from setuptools import setup here = os.path.dirname(__file__) + def get_long_desc(): return open(os.path.join(here, 'README.rst')).read() + # Function borrowed from carljm. def get_version(): fh = open(os.path.join(here, "faq", "__init__.py")) From 00507a755a0bebca17515da34a82adc78ab8d533 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 28 Sep 2017 07:12:53 -0500 Subject: [PATCH 09/21] [0.9] Update fixtures data to include TZ --- faq/fixtures/test_data.json | 268 ++++++++++++++++++------------------ 1 file changed, 134 insertions(+), 134 deletions(-) diff --git a/faq/fixtures/test_data.json b/faq/fixtures/test_data.json index 7981377..9f9033e 100644 --- a/faq/fixtures/test_data.json +++ b/faq/fixtures/test_data.json @@ -1,206 +1,206 @@ [ { - "pk": 3, - "model": "faq.topic", + "pk": 3, + "model": "faq.topic", "fields": { - "status": 3, - "description": "All about \u2026 US!", - "title": "About us", - "created": "2011-06-07 15:21:44", - "modified": "2011-06-07 15:30:31", + "status": 3, + "description": "All about \u2026 US!", + "title": "About us", + "created": "2011-06-07 15:21:44Z", + "modified": "2011-06-07 15:30:31Z", "sites": [ 2 - ], + ], "slug": "about-us" } - }, + }, { - "pk": 5, - "model": "faq.topic", + "pk": 5, + "model": "faq.topic", "fields": { - "status": 3, - "description": "Sshhh!", - "title": "Black market items", - "created": "2011-06-07 15:26:07", - "modified": "2011-06-07 15:26:07", + "status": 3, + "description": "Sshhh!", + "title": "Black market items", + "created": "2011-06-07 15:26:07Z", + "modified": "2011-06-07 15:26:07Z", "sites": [ 1 - ], + ], "slug": "black-market-items" } - }, + }, { - "pk": 2, - "model": "faq.topic", + "pk": 2, + "model": "faq.topic", "fields": { - "status": 2, - "description": "All about returning an unwanted item.", - "title": "Returns", - "created": "2011-06-07 11:33:25", - "modified": "2011-06-07 15:29:41", + "status": 2, + "description": "All about returning an unwanted item.", + "title": "Returns", + "created": "2011-06-07 11:33:25Z", + "modified": "2011-06-07 15:29:41Z", "sites": [ - 1, + 1, 2 - ], + ], "slug": "returns" } - }, + }, { - "pk": 1, - "model": "faq.topic", + "pk": 1, + "model": "faq.topic", "fields": { - "status": 2, - "description": "All about how we ship your purchase to you.", - "title": "Shipping", - "created": "2011-06-07 11:31:15", - "modified": "2011-06-07 15:28:46", + "status": 2, + "description": "All about how we ship your purchase to you.", + "title": "Shipping", + "created": "2011-06-07 11:31:15Z", + "modified": "2011-06-07 15:28:46Z", "sites": [ 1 - ], + ], "slug": "shipping" } - }, + }, { - "pk": 4, - "model": "faq.topic", + "pk": 4, + "model": "faq.topic", "fields": { - "status": 1, - "description": "About our website.", - "title": "Website", - "created": "2011-06-07 15:23:59", - "modified": "2011-06-07 15:23:59", + "status": 1, + "description": "About our website.", + "title": "Website", + "created": "2011-06-07 15:23:59Z", + "modified": "2011-06-07 15:23:59Z", "sites": [ - 1, + 1, 2 - ], + ], "slug": "website" } - }, + }, { - "pk": 4, - "model": "faq.question", + "pk": 4, + "model": "faq.question", "fields": { - "status": 2, - "created": "2011-06-07 15:21:44", - "ordering": 1, - "question": "Are you hiring?", - "modified": "2011-06-07 15:21:44", - "topic": 3, - "answer": "Yes and no. If you are awesome, yes. If you are less than awesome, then not so much.", + "status": 2, + "created": "2011-06-07 15:21:44Z", + "ordering": 1, + "question": "Are you hiring?", + "modified": "2011-06-07 15:21:44Z", + "topic": 3, + "answer": "Yes and no. If you are awesome, yes. If you are less than awesome, then not so much.", "slug": "are-you-hiring" } - }, + }, { - "pk": 5, - "model": "faq.question", + "pk": 5, + "model": "faq.question", "fields": { - "status": 2, - "created": "2011-06-07 15:23:59", - "ordering": 1, - "question": "Do you have an SLA?", - "modified": "2011-06-07 15:23:59", - "topic": 4, - "answer": "No, because people with those get hosed.", + "status": 2, + "created": "2011-06-07 15:23:59Z", + "ordering": 1, + "question": "Do you have an SLA?", + "modified": "2011-06-07 15:23:59Z", + "topic": 4, + "answer": "No, because people with those get hosed.", "slug": "do-you-have-an-sla" } - }, + }, { - "pk": 6, - "model": "faq.question", + "pk": 6, + "model": "faq.question", "fields": { - "status": 2, - "created": "2011-06-07 15:26:07", - "ordering": 1, - "question": "How do you acquire black market items?", - "modified": "2011-06-07 15:26:07", - "topic": 5, - "answer": "Very, very carefully", + "status": 2, + "created": "2011-06-07 15:26:07Z", + "ordering": 1, + "question": "How do you acquire black market items?", + "modified": "2011-06-07 15:26:07Z", + "topic": 5, + "answer": "Very, very carefully", "slug": "how-do-you-acquire-black-market-items" } - }, + }, { - "pk": 3, - "model": "faq.question", + "pk": 3, + "model": "faq.question", "fields": { - "status": 2, - "created": "2011-06-07 11:33:25", - "ordering": 1, - "question": "How long do I have to return my item?", - "modified": "2011-06-07 11:33:25", - "topic": 2, - "answer": "We are very liberal with this, but it is generally 20-30 minutes.", + "status": 2, + "created": "2011-06-07 11:33:25Z", + "ordering": 1, + "question": "How long do I have to return my item?", + "modified": "2011-06-07 11:33:25Z", + "topic": 2, + "answer": "We are very liberal with this, but it is generally 20-30 minutes.", "slug": "how-long-do-i-have-to-return-my-item" } - }, + }, { - "pk": 1, - "model": "faq.question", + "pk": 1, + "model": "faq.question", "fields": { - "status": 2, - "created": "2011-06-07 11:31:15", - "ordering": 1, - "question": "How much does shipping cost?", - "modified": "2011-06-07 11:31:15", - "topic": 1, - "answer": "Not much. At last check it was just shy of one frillion dollars.", + "status": 2, + "created": "2011-06-07 11:31:15Z", + "ordering": 1, + "question": "How much does shipping cost?", + "modified": "2011-06-07 11:31:15Z", + "topic": 1, + "answer": "Not much. At last check it was just shy of one frillion dollars.", "slug": "how-much-does-shipping-cost" } - }, + }, { - "pk": 2, - "model": "faq.question", + "pk": 2, + "model": "faq.question", "fields": { - "status": 2, - "created": "2011-06-07 11:32:14", - "ordering": 2, - "question": "How fast will my item arrive?", - "modified": "2011-06-07 11:32:14", - "topic": 1, - "answer": "Sometime next week. We think.", + "status": 2, + "created": "2011-06-07 11:32:14Z", + "ordering": 2, + "question": "How fast will my item arrive?", + "modified": "2011-06-07 11:32:14Z", + "topic": 1, + "answer": "Sometime next week. We think.", "slug": "how-fast-will-my-item-arrive" } - }, + }, { - "pk": 7, - "model": "faq.question", + "pk": 7, + "model": "faq.question", "fields": { - "status": 1, - "created": "2011-06-07 15:28:46", - "ordering": 3, - "question": "In what color box do you ship?", - "modified": "2011-06-07 15:28:46", - "topic": 1, - "answer": "Green, because it is good for the environment. Go us!", + "status": 1, + "created": "2011-06-07 15:28:46Z", + "ordering": 3, + "question": "In what color box do you ship?", + "modified": "2011-06-07 15:28:46Z", + "topic": 1, + "answer": "Green, because it is good for the environment. Go us!", "slug": "in-what-color-box-do-you-ship" } - }, + }, { - "pk": 8, - "model": "faq.question", + "pk": 8, + "model": "faq.question", "fields": { - "status": 3, - "created": "2011-06-07 15:28:46", - "ordering": 4, - "question": "What carrier do you use?", - "modified": "2011-06-07 15:28:46", - "topic": 1, - "answer": "Whomever saves us the most money.", + "status": 3, + "created": "2011-06-07 15:28:46Z", + "ordering": 4, + "question": "What carrier do you use?", + "modified": "2011-06-07 15:28:46Z", + "topic": 1, + "answer": "Whomever saves us the most money.", "slug": "what-carrier-do-you-use" } - }, + }, { - "pk": 1, - "model": "sites.site", + "pk": 1, + "model": "sites.site", "fields": { - "domain": "example.com", + "domain": "example.com", "name": "example.com" } - }, + }, { - "pk": 2, - "model": "sites.site", + "pk": 2, + "model": "sites.site", "fields": { - "domain": "example.us", + "domain": "example.us", "name": "example.us" } } From e5b63d210a595a061eefbea128ac3be13bbe7d86 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 28 Sep 2017 07:14:26 -0500 Subject: [PATCH 10/21] [0.9] Refactor urls and views. Shallow URL patterns no longer work in latest Django versions. Moved views into one file, because the previous separation wasn't necessary. --- faq/urls/deep.py | 11 +++---- faq/urls/normal.py | 12 +++---- faq/urls/shallow.py | 18 ---------- faq/views.py | 76 +++++++++++++++++++++++++++++++++++++++++++ faq/views/__init__.py | 1 - faq/views/deep.py | 19 ----------- faq/views/normal.py | 35 -------------------- faq/views/shallow.py | 47 -------------------------- 8 files changed, 85 insertions(+), 134 deletions(-) delete mode 100644 faq/urls/shallow.py create mode 100644 faq/views.py delete mode 100644 faq/views/__init__.py delete mode 100644 faq/views/deep.py delete mode 100644 faq/views/normal.py delete mode 100644 faq/views/shallow.py diff --git a/faq/urls/deep.py b/faq/urls/deep.py index ef0ce38..75e0c7c 100644 --- a/faq/urls/deep.py +++ b/faq/urls/deep.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- -from django.conf.urls import patterns, url +from django.conf.urls import url -from faq.views.shallow import TopicListView -from faq.views.normal import TopicDetailView -from faq.views.deep import QuestionDetailView +from faq.views import TopicListView, TopicDetailView, QuestionDetailView # Include these patterns if you want URLs like: @@ -14,8 +12,7 @@ # /faq/topic/question/ # -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^$', TopicListView.as_view(), name='faq-topic-list'), url( r'^(?P[-\w]+)/$', @@ -25,4 +22,4 @@ r'^(?P[-\w]+)/(?P[-\w]+)/$', QuestionDetailView.as_view(), name='faq-question-detail'), -) +] diff --git a/faq/urls/normal.py b/faq/urls/normal.py index c221f73..927c35a 100644 --- a/faq/urls/normal.py +++ b/faq/urls/normal.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- -from django.conf.urls import patterns, url +from django.conf.urls import url -from faq.views.shallow import TopicListView -from faq.views.normal import TopicDetailView, question_detail +from faq.views import TopicListView, TopicDetailView, question_detail # Include these patterns if you want URLs like: @@ -13,15 +12,14 @@ # /faq/topic/#question # -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^$', TopicListView.as_view(), name='faq-topic-list'), url( r'^(?P[-\w]+)/$', TopicDetailView.as_view(), name='faq-topic-detail'), url( - r'^(?P[-\w]+)/(?P[-\w]+)/$', + r'^(?P[-\w]+)/#(?P[-\w]+)/$', question_detail, name='faq-question-detail'), -) +] diff --git a/faq/urls/shallow.py b/faq/urls/shallow.py deleted file mode 100644 index 06a9942..0000000 --- a/faq/urls/shallow.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- - -from django.conf.urls import patterns, url -from faq.views.shallow import TopicListView, topic_detail, question_detail - -# Include these patterns if you want URLs like: -# -# /faq/ -# /faq/#topic -# /faq/#question -# - -urlpatterns = patterns( - '', - url(r'^$', TopicListView.as_view(), name='faq-topic-list'), - url(r'^#(?P[-\w]+)$', topic_detail, name='faq-topic-detail'), - url(r'^#(?P[-\w]+)$', question_detail, name='faq-question-detail'), -) diff --git a/faq/views.py b/faq/views.py new file mode 100644 index 0000000..ceff64b --- /dev/null +++ b/faq/views.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +from django.core.urlresolvers import reverse +from django.shortcuts import get_object_or_404, redirect +from django.views.generic.detail import DetailView +from django.views.generic.list import ListView + +from faq.models import Topic, Question + + +def _fragmentify(model, slug, url=None): + get_object_or_404(model.objects.published().filter(slug=slug)) + url = url or reverse('faq-topic-list') + fragment = '#%s' % slug + + return redirect(url + fragment, permanent=True) + + +class QuestionDetailView(DetailView): + model = Question + + def get_context_data(self, **kwargs): + context = super(QuestionDetailView, self).get_context_data(**kwargs) + context['topic'] = Topic.objects.published().get( + slug=context['question'].topic.slug + ) + return context + + def get_template_names(self): + return ['faq/question_detail.html'] + + +class TopicDetailView(DetailView): + model = Topic + + def get_context_data(self, **kwargs): + context = super(TopicDetailView, self).get_context_data(**kwargs) + context['question_list'] = Question.objects.published().filter( + topic__slug=context['topic'].slug + ) + return context + + def get_template_names(self): + return ['faq/topic_detail.html'] + + +class TopicListView(ListView): + model = Topic + + def get_template_names(self): + return ['faq/topic_list.html'] + + +def question_detail(request, topic_slug, slug): + """ + A detail view of a Question. + + Simply redirects to a detail page for the related :model:`faq.Topic` + (:view:`faq.views.topic_detail`) with the addition of a fragment + identifier that links to the given :model:`faq.Question`. + E.g. ``/faq/topic-slug/#question-slug``. + + """ + url = reverse('faq-topic-detail', kwargs={'slug': topic_slug}) + return _fragmentify(Question, slug, url) + + +def topic_detail(request, slug): + """ + A detail view of a Topic + + Simply redirects to :view:`faq.views.topic_list` with the addition of + a fragment identifier that links to the given :model:`faq.Topic`. + E.g., ``/faq/#topic-slug``. + + """ + return _fragmentify(Topic, slug) diff --git a/faq/views/__init__.py b/faq/views/__init__.py deleted file mode 100644 index 40a96af..0000000 --- a/faq/views/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/faq/views/deep.py b/faq/views/deep.py deleted file mode 100644 index 77d2a76..0000000 --- a/faq/views/deep.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- - -from faq.models import Topic, Question - -from django.views.generic.detail import DetailView - - -class QuestionDetailView(DetailView): - model = Question - - def get_context_data(self, **kwargs): - context = super(QuestionDetailView, self).get_context_data(**kwargs) - context['topic'] = Topic.objects.published().get( - slug=context['question'].topic.slug - ) - return context - - def get_template_names(self): - return ['faq/question_detail.html'] diff --git a/faq/views/normal.py b/faq/views/normal.py deleted file mode 100644 index ed6995c..0000000 --- a/faq/views/normal.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- - -from django.core.urlresolvers import reverse -from django.views.generic.detail import DetailView - -from faq.models import Topic, Question -from faq.views.shallow import _fragmentify - - -class TopicDetailView(DetailView): - model = Topic - - def get_context_data(self, **kwargs): - context = super(TopicDetailView, self).get_context_data(**kwargs) - context['question_list'] = Question.objects.published().filter( - topic__slug=context['topic'].slug - ) - return context - - def get_template_names(self): - return ['faq/topic_detail.html'] - - -def question_detail(request, topic_slug, slug): - """ - A detail view of a Question. - - Simply redirects to a detail page for the related :model:`faq.Topic` - (:view:`faq.views.topic_detail`) with the addition of a fragment - identifier that links to the given :model:`faq.Question`. - E.g. ``/faq/topic-slug/#question-slug``. - - """ - url = reverse('faq-topic-detail', kwargs={'slug': topic_slug}) - return _fragmentify(Question, slug, url) diff --git a/faq/views/shallow.py b/faq/views/shallow.py deleted file mode 100644 index 2a13e15..0000000 --- a/faq/views/shallow.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- - -from django.core.urlresolvers import reverse -from django.shortcuts import get_object_or_404, redirect - -from django.views.generic.list import ListView - -from faq.models import Topic, Question - - -def _fragmentify(model, slug, url=None): - get_object_or_404(model.objects.published().filter(slug=slug)) - url = url or reverse('faq-topic-list') - fragment = '#%s' % slug - - return redirect(url + fragment, permanent=True) - - -class TopicListView(ListView): - model = Topic - - def get_template_names(self): - return ['faq/topic_list.html'] - - -def topic_detail(request, slug): - """ - A detail view of a Topic - - Simply redirects to :view:`faq.views.topic_list` with the addition of - a fragment identifier that links to the given :model:`faq.Topic`. - E.g., ``/faq/#topic-slug``. - - """ - return _fragmentify(Topic, slug) - - -def question_detail(request, topic_slug, slug): - """ - A detail view of a Question. - - Simply redirects to :view:`faq.views.topic_list` with the addition of - a fragment identifier that links to the given :model:`faq.Question`. - E.g. ``/faq/#question-slug``. - - """ - return _fragmentify(Question, slug) From 0b7f39dac2128912deb49336c0a74d6820dd40d4 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 28 Sep 2017 07:15:10 -0500 Subject: [PATCH 11/21] [0.9] Updated tests to override urls appropriately --- faq/tests/__init__.py | 46 ++++++++++++------------------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/faq/tests/__init__.py b/faq/tests/__init__.py index 6cad6ac..60f9b1f 100644 --- a/faq/tests/__init__.py +++ b/faq/tests/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from django.test import TestCase +from django.test import TestCase, override_settings from django.contrib.sites.models import Site from faq.settings import DRAFTED @@ -12,6 +12,7 @@ # Test admin actions? ## + class BaseTestCase(TestCase): """""" @@ -55,13 +56,13 @@ class ManagerTestCase(BaseTestCase): class ModelsTestCase(BaseTestCase): """""" - def testManager(self): + def test_manager(self): # Because of our sublcassing with the models, be certain that the # manager is wired up correctly. self.assertTrue(isinstance(Topic.objects, OnSiteManager)) self.assertTrue(isinstance(Question.objects, OnSiteManager)) - def testUnicode(self): + def test_unicode(self): # Ensure that we don't absent-mindedly change what the `__unicode__()` # method returns. self.assertEqual(self.topics['new'].__unicode__(), @@ -69,18 +70,18 @@ def testUnicode(self): self.assertEqual(self.questions['new1'].__unicode__(), self.questions['new1'].question) - def testDefaultStatus(self): + def test_default_status(self): # Items created without choosing a status should be drafted by default. self.assertEqual(self.topics['new'].status, DRAFTED) self.assertEqual(self.questions['new1'].status, DRAFTED) - def testSlugOnSave(self): + def test_slug_on_save(self): # Be sure we are properly creating slugs for questions that are created # without them (those created as an inline to a topic). self.assertEqual(self.questions['new1'].slug, u'where-am-i') self.assertEqual(self.questions['new2'].slug, u'who-are-you') - def testOrderingOnSave(self): + def test_ordering_on_save(self): # Be sure we are properly calculating and filling the ordering field # when a user leaves it blank. self.assertEqual(self.questions['new1'].ordering, 1) @@ -107,27 +108,9 @@ def setUp(self): } -class ViewsShallowTestCase(ViewsBaseTestCase): - - urls = 'faq.urls.shallow' - - def testTopicDetail(self): - # Redirects to a fragment identifier on the topic list. - self.assertRedirects(self.responses['topic_detail'], - '/#shipping', status_code=301) - - def testQuestionDetail(self): - # Redirects to a fragment identifier on the topic list. - self.assertRedirects(self.responses['question_detail'], - '/#how-much-does-shipping-cost', status_code=301) - - -class ViewsNormalTestCase(ViewsShallowTestCase): - """""" - - urls = 'faq.urls.normal' - - def testTopicDetail(self): +@override_settings(ROOT_URLCONF='faq.urls.normal') +class ViewsNormalTestCase(ViewsBaseTestCase): + def test_topic_detail(self): # Does not redirect. self.assertEqual(self.responses['topic_detail'].status_code, 200) # Check for our extra_context. @@ -135,18 +118,15 @@ def testTopicDetail(self): self.assertEqual(list(self.responses['topic_detail'].context['question_list']), list(self.topics['published'].questions.published())) - def testQuestionDetail(self): + def test_question_detail(self): # Redirects to a fragment identifier on the topic detail. self.assertRedirects(self.responses['question_detail'], '/shipping/#how-much-does-shipping-cost', status_code=301) +@override_settings(ROOT_URLCONF='faq.urls.deep') class ViewsDeepTestCase(ViewsNormalTestCase): - """""" - - urls = 'faq.urls.deep' - - def testQuestionDetail(self): + def test_question_detail(self): # Does not redirect. self.assertEqual(self.responses['question_detail'].status_code, 200) # Check for our extra_context. From c1577184404f7c417ed053df7568460c39a8cf58 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 28 Sep 2017 07:15:39 -0500 Subject: [PATCH 12/21] [0.9] Python 3 update --- faq/forms.py | 3 ++- faq/models.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/faq/forms.py b/faq/forms.py index afcab17..20d44eb 100644 --- a/faq/forms.py +++ b/faq/forms.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from builtins import object from django import forms from faq.models import Question @@ -8,7 +9,7 @@ class QuestionForm(forms.ModelForm): """A form whose only purpose is to manage fields for the QuestionInline.""" - class Meta: + class Meta(object): # InlineModelAdmin does not support ``fields``, so if we want to order # the fields in an InlineModelAdmin, we must do so with a custom # ModelForm. This is not ideal, but at least it gets the job done. diff --git a/faq/models.py b/faq/models.py index 8d6c4c2..16fa994 100644 --- a/faq/models.py +++ b/faq/models.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from builtins import object from django.db import models from django.conf import settings from django.contrib.sites.models import Site @@ -86,7 +87,7 @@ class FAQBase(models.Model): objects = OnSiteManager() - class Meta: + class Meta(object): abstract = True get_latest_by = 'modified' From 46bfc008486f0c6b5054c9bd2cfdbc4fc1b7f599 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 28 Sep 2017 07:16:10 -0500 Subject: [PATCH 13/21] [0.9] Add tox support --- .gitignore | 4 +++- tox.ini | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index 24a6676..69fad4a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ build/ dist/ docs/_build MANIFEST -*.egg-info \ No newline at end of file +*.egg-info +.tox/ +.cache/ \ No newline at end of file diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..0927903 --- /dev/null +++ b/tox.ini @@ -0,0 +1,14 @@ +# Tox (https://tox.readthedocs.io/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +envlist = py{27,36}-django{19,110,111} + +[testenv] +commands = python runtests.py {posargs} +deps = + django19: Django<1.10 + django110: Django<1.11 + django111: Django<2.0 From 2a8e4fc3e38a63a7108043da14ac3f2d107ba318 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 28 Sep 2017 07:16:38 -0500 Subject: [PATCH 14/21] [0.9] Update packaging info, versioning info and runtest script --- faq/__init__.py | 27 +++++++++++++----- runtests.py | 73 +++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 26 ++++-------------- 3 files changed, 98 insertions(+), 28 deletions(-) create mode 100644 runtests.py diff --git a/faq/__init__.py b/faq/__init__.py index ae782f3..09945e5 100644 --- a/faq/__init__.py +++ b/faq/__init__.py @@ -15,10 +15,23 @@ """ -from django.utils.translation import ugettext_lazy as _ - - -__version__ = '0.9b2' - -# Mark the app_label for translation. -_(u'faq') +__version_info__ = { + 'major': 0, + 'minor': 9, + 'micro': 0, + 'releaselevel': 'final', + 'serial': 1 +} + + +def get_version(short=False): + assert __version_info__['releaselevel'] in ('alpha', 'beta', 'final') + vers = ["%(major)i.%(minor)i" % __version_info__, ] + if __version_info__['micro']: + vers.append(".%(micro)i" % __version_info__) + if __version_info__['releaselevel'] != 'final' and not short: + vers.append('%s%i' % ( + __version_info__['releaselevel'][0], __version_info__['serial'])) + return ''.join(vers) + +__version__ = get_version() diff --git a/runtests.py b/runtests.py new file mode 100644 index 0000000..920c331 --- /dev/null +++ b/runtests.py @@ -0,0 +1,73 @@ +import sys + +try: + from django.conf import settings + + settings.configure( + DEBUG=True, + LANGUAGE_CODE='en-us', + USE_TZ=True, + DATABASES={ + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': 'djangocms_youtube', + } + }, + ROOT_URLCONF='faq.urls.normal', + INSTALLED_APPS=[ + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sites', + 'faq', + ], + SITE_ID=1, + NOSE_ARGS=['-s'], + TEMPLATES=[{ + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + '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.template.context_processors.request", + "django.contrib.messages.context_processors.messages", + ], + }, + }, ] + + ) + + try: + import django + setup = django.setup + except AttributeError: + pass + else: + setup() + +except ImportError: + import traceback + traceback.print_exc() + raise ImportError('To fix this error, run: pip install -r requirements-test.txt') + + +def run_tests(*test_args): + from django.test.utils import get_runner + if not test_args: + test_args = ['faq.tests'] + + # Run tests + TestRunner = get_runner(settings) # NOQA + test_runner = TestRunner() + failures = test_runner.run_tests(test_args) + + if failures: + sys.exit(failures) + + +if __name__ == '__main__': + run_tests(*sys.argv[1:]) diff --git a/setup.py b/setup.py index 2159a14..6b11b29 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- - import os -from setuptools import setup +from setuptools import setup, find_packages here = os.path.dirname(__file__) @@ -12,32 +11,16 @@ def get_long_desc(): return open(os.path.join(here, 'README.rst')).read() -# Function borrowed from carljm. -def get_version(): - fh = open(os.path.join(here, "faq", "__init__.py")) - try: - for line in fh.readlines(): - if line.startswith("__version__ ="): - return line.split("=")[1].strip().strip("'") - finally: - fh.close() - setup( name='django-faq', - version=get_version(), + version=__import__('faq').get_version().replace(' ', '-'), description='Frequently Asked Question (FAQ) management for Django apps.', - url='https://github.com/benspaulding/django-faq/', + url='https://github.com/natgeosociety/django-faq/', author='Ben Spaulding', author_email='ben@benspaulding.us', license='BSD', - download_url='http://github.com/benspaulding/django-faq/tarball/v%s' % get_version(), long_description=get_long_desc(), - packages=[ - 'faq', - 'faq.tests', - 'faq.urls', - 'faq.views', - ], + packages=find_packages(exclude=['example*']), package_data={ 'faq': [ 'fixtures/*', @@ -46,6 +29,7 @@ def get_version(): 'templates/search/indexes/faq/*', ], }, + install_requires=['future'], classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', From 7532d77350c479c09eb44fa61a0b33a3df8c56c0 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 28 Sep 2017 07:23:21 -0500 Subject: [PATCH 15/21] Updated setup.py to include tagging and publishing commands --- setup.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6b11b29..99207cb 100644 --- a/setup.py +++ b/setup.py @@ -1,19 +1,31 @@ # -*- coding: utf-8 -*- import os +import sys from setuptools import setup, find_packages here = os.path.dirname(__file__) +version = __import__('faq').get_version() def get_long_desc(): return open(os.path.join(here, 'README.rst')).read() +if sys.argv[-1] == 'publish': + os.system('python setup.py bdist_wheel upload -r natgeo') + print("You probably want to also tag the version now:") + print(" python setup.py tag") + sys.exit() +elif sys.argv[-1] == 'tag': + cmd = "git tag -a %s -m 'version %s';git push --tags" % (version, version) + os.system(cmd) + sys.exit() + setup( name='django-faq', - version=__import__('faq').get_version().replace(' ', '-'), + version=version.replace(' ', '-'), description='Frequently Asked Question (FAQ) management for Django apps.', url='https://github.com/natgeosociety/django-faq/', author='Ben Spaulding', From a3ba9b14949419cfa1899d20fe2392500dc9c3d4 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 28 Sep 2017 08:15:58 -0500 Subject: [PATCH 16/21] [0.9.1] Update packaging to remove rogue views directory --- faq/__init__.py | 2 +- faq/tests/__init__.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/faq/__init__.py b/faq/__init__.py index 09945e5..c1c63ad 100644 --- a/faq/__init__.py +++ b/faq/__init__.py @@ -18,7 +18,7 @@ __version_info__ = { 'major': 0, 'minor': 9, - 'micro': 0, + 'micro': 1, 'releaselevel': 'final', 'serial': 1 } diff --git a/faq/tests/__init__.py b/faq/tests/__init__.py index 60f9b1f..75cc2c7 100644 --- a/faq/tests/__init__.py +++ b/faq/tests/__init__.py @@ -110,6 +110,10 @@ def setUp(self): @override_settings(ROOT_URLCONF='faq.urls.normal') class ViewsNormalTestCase(ViewsBaseTestCase): + def test_import_view(self): + # This was causing an issue for some reason + from faq.views import TopicListView, TopicDetailView, question_detail # NOQA + def test_topic_detail(self): # Does not redirect. self.assertEqual(self.responses['topic_detail'].status_code, 200) From 819597b3ebe52e27881cf41291d9fb5e5d72e6fc Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 28 Sep 2017 15:58:47 -0500 Subject: [PATCH 17/21] Added coverage.py to the tox config and ignored the artifacts for git --- .gitignore | 5 ++++- tox.ini | 12 ++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 69fad4a..2fe98c5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ docs/_build MANIFEST *.egg-info .tox/ -.cache/ \ No newline at end of file +.cache/ +.coverage +junit-*.xml +coverage.xml \ No newline at end of file diff --git a/tox.ini b/tox.ini index 0927903..c7a8fb8 100644 --- a/tox.ini +++ b/tox.ini @@ -4,11 +4,19 @@ # and then run "tox" from this directory. [tox] -envlist = py{27,36}-django{19,110,111} +envlist = + py{27,36}-django{19,110,111} + coverage-report [testenv] -commands = python runtests.py {posargs} +commands = coverage run --source faq runtests.py {posargs} deps = + coverage django19: Django<1.10 django110: Django<1.11 django111: Django<2.0 + +[testenv:coverage-report] +commands = + coverage report -m + coverage xml From 3e6cb515de6cc0677796a689a70f8600712d9860 Mon Sep 17 00:00:00 2001 From: Arif Cengic Date: Fri, 20 Dec 2019 10:55:36 -0500 Subject: [PATCH 18/21] run 2to3 conversion --- docs/conf.py | 12 +++++------ faq/admin.py | 14 ++++++------- faq/migrations/0001_initial.py | 2 +- faq/models.py | 38 +++++++++++++++++----------------- faq/settings.py | 6 +++--- faq/tests/__init__.py | 16 +++++++------- 6 files changed, 44 insertions(+), 44 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index d78d4b7..f701f1f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -46,8 +46,8 @@ master_doc = 'index' # General information about the project. -project = u'django-faq' -copyright = u'2012, Ben Spaulding' +project = 'django-faq' +copyright = '2012, Ben Spaulding' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -184,8 +184,8 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'django-faq.tex', u'django-faq Documentation', - u'Ben Spaulding', 'manual'), + ('index', 'django-faq.tex', 'django-faq Documentation', + 'Ben Spaulding', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -217,8 +217,8 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'django-faq', u'django-faq Documentation', - [u'Ben Spaulding'], 1) + ('index', 'django-faq', 'django-faq Documentation', + ['Ben Spaulding'], 1) ] diff --git a/faq/admin.py b/faq/admin.py index 0716c46..e6a7d98 100644 --- a/faq/admin.py +++ b/faq/admin.py @@ -25,7 +25,7 @@ def update_status(modeladmin, request, queryset, status): obj.save() # Now log what happened. # Use ugettext_noop() 'cause this is going straight into the db. - log_message = ugettext_noop(u'Changed status to \'%s\'.' % + log_message = ugettext_noop('Changed status to \'%s\'.' % obj.get_status_display()) modeladmin.log_change(request, obj, log_message) @@ -38,8 +38,8 @@ def update_status(modeladmin, request, queryset, status): if not message_dict['count'] == 1: message_dict['object'] = modeladmin.model._meta.verbose_name_plural user_message = ungettext( - u'%(count)s %(object)s was successfully %(verb)s.', - u'%(count)s %(object)s were successfully %(verb)s.', + '%(count)s %(object)s was successfully %(verb)s.', + '%(count)s %(object)s were successfully %(verb)s.', message_dict['count']) % message_dict modeladmin.message_user(request, user_message) @@ -52,19 +52,19 @@ def update_status(modeladmin, request, queryset, status): def draft(modeladmin, request, queryset): """Admin action for setting status of selected items to 'drafted'.""" return update_status(modeladmin, request, queryset, DRAFTED) -draft.short_description = _(u'Draft selected %(verbose_name_plural)s') +draft.short_description = _('Draft selected %(verbose_name_plural)s') def publish(modeladmin, request, queryset): """Admin action for setting status of selected items to 'published'.""" return update_status(modeladmin, request, queryset, PUBLISHED) -publish.short_description = _(u'Publish selected %(verbose_name_plural)s') +publish.short_description = _('Publish selected %(verbose_name_plural)s') def remove(modeladmin, request, queryset): """Admin action for setting status of selected items to 'removed'.""" return update_status(modeladmin, request, queryset, REMOVED) -remove.short_description = _(u'Remove selected %(verbose_name_plural)s') +remove.short_description = _('Remove selected %(verbose_name_plural)s') # Inlines. @@ -98,7 +98,7 @@ class TopicAdmin(FAQAdminBase): def question_count(self, obj): """Returns the total number of Questions for this topic.""" return obj.questions.count() - question_count.short_description = _(u'No. of Questions') + question_count.short_description = _('No. of Questions') class QuestionAdmin(FAQAdminBase): diff --git a/faq/migrations/0001_initial.py b/faq/migrations/0001_initial.py index 44bd2fb..6d5b18a 100644 --- a/faq/migrations/0001_initial.py +++ b/faq/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/faq/models.py b/faq/models.py index 16fa994..4f10b3b 100644 --- a/faq/models.py +++ b/faq/models.py @@ -74,15 +74,15 @@ def removed(self): class FAQBase(models.Model): """A model holding information common to Topics and Questions.""" - created = models.DateTimeField(_(u'date created'), auto_now_add=True) - modified = models.DateTimeField(_(u'date modified'), auto_now=True) + created = models.DateTimeField(_('date created'), auto_now_add=True) + modified = models.DateTimeField(_('date modified'), auto_now=True) status = models.IntegerField( - _(u'status'), + _('status'), choices=STATUS_CHOICES, # TODO: Genericize/fix the help_text. db_index=True, default=DRAFTED, - help_text=_(u'Only objects with "published" ' + help_text=_('Only objects with "published" ' 'status will be displayed publicly.')) objects = OnSiteManager() @@ -95,22 +95,22 @@ class Meta(object): class Topic(FAQBase): """A topic that a Question can belong to.""" - title = models.CharField(_(u'title'), max_length=255) - slug = models.SlugField(_(u'slug'), unique=True, help_text=_(u'Used in \ + title = models.CharField(_('title'), max_length=255) + slug = models.SlugField(_('slug'), unique=True, help_text=_('Used in \ the URL for the topic. Must be unique.')) description = models.TextField( - _(u'description'), + _('description'), blank=True, - help_text=_(u'A short description of this topic.')) + help_text=_('A short description of this topic.')) sites = models.ManyToManyField( Site, - verbose_name=_(u'sites'), + verbose_name=_('sites'), related_name='faq_topics') class Meta(FAQBase.Meta): ordering = ('title', 'slug') - verbose_name = _(u'topic') - verbose_name_plural = _(u'topics') + verbose_name = _('topic') + verbose_name_plural = _('topics') def __unicode__(self): return self.title @@ -123,26 +123,26 @@ def get_absolute_url(self): class Question(FAQBase): """A frequently asked question.""" - question = models.CharField(_(u'question'), max_length=255) - slug = models.SlugField(_(u'slug'), unique=True, help_text=_(u'Used in \ + question = models.CharField(_('question'), max_length=255) + slug = models.SlugField(_('slug'), unique=True, help_text=_('Used in \ the URL for the Question. Must be unique.')) - answer = models.TextField(_(u'answer')) + answer = models.TextField(_('answer')) topic = models.ForeignKey( Topic, - verbose_name=_(u'topic'), + verbose_name=_('topic'), related_name='questions') ordering = models.PositiveSmallIntegerField( - _(u'ordering'), + _('ordering'), blank=True, db_index=True, - help_text=_(u'An integer used to order the question \ + help_text=_('An integer used to order the question \ amongst others related to the same topic. If not given this \ question will be last in the list.')) class Meta(FAQBase.Meta): ordering = ('ordering', 'question', 'slug') - verbose_name = _(u'question') - verbose_name_plural = _(u'questions') + verbose_name = _('question') + verbose_name_plural = _('questions') def __unicode__(self): return self.question diff --git a/faq/settings.py b/faq/settings.py index cd308de..6f2bb9b 100644 --- a/faq/settings.py +++ b/faq/settings.py @@ -13,9 +13,9 @@ REMOVED = getattr(settings, 'FAQ_REMOVED', 3) STATUS_CHOICES = ( - (DRAFTED, _(u'drafted')), - (PUBLISHED, _(u'published')), - (REMOVED, _(u'removed')), + (DRAFTED, _('drafted')), + (PUBLISHED, _('published')), + (REMOVED, _('removed')), ) STATUS_CHOICES = getattr(settings, 'FAQ_STATUS_CHOICES', STATUS_CHOICES) diff --git a/faq/tests/__init__.py b/faq/tests/__init__.py index 75cc2c7..6e0af41 100644 --- a/faq/tests/__init__.py +++ b/faq/tests/__init__.py @@ -23,8 +23,8 @@ def setUp(self): # data because we will be testing for the state of something newly # created, which the test data does not contain, obviously. self.topics = { - 'new': Site.objects.get_current().faq_topics.create(title=u'Test Topic', - slug=u'test-topic'), + 'new': Site.objects.get_current().faq_topics.create(title='Test Topic', + slug='test-topic'), 'drafted': Topic.objects.get(slug='website'), 'published': Topic.objects.get(slug='shipping'), 'removed': Topic.objects.get(slug='black-market-items'), @@ -32,10 +32,10 @@ def setUp(self): } self. questions = { - 'new1': self.topics['new'].questions.create(question=u'Where am I?', - answer=u'That is classified.'), - 'new2': self.topics['new'].questions.create(question=u'Who are you?', - answer=u'I cannot say.'), + 'new1': self.topics['new'].questions.create(question='Where am I?', + answer='That is classified.'), + 'new2': self.topics['new'].questions.create(question='Who are you?', + answer='I cannot say.'), 'drafted': Question.objects.get(slug='in-what-color-box-do-you-ship'), 'published': Question.objects.get(slug='how-much-does-shipping-cost'), 'removed': Question.objects.get(slug='what-carrier-do-you-use'), @@ -78,8 +78,8 @@ def test_default_status(self): def test_slug_on_save(self): # Be sure we are properly creating slugs for questions that are created # without them (those created as an inline to a topic). - self.assertEqual(self.questions['new1'].slug, u'where-am-i') - self.assertEqual(self.questions['new2'].slug, u'who-are-you') + self.assertEqual(self.questions['new1'].slug, 'where-am-i') + self.assertEqual(self.questions['new2'].slug, 'who-are-you') def test_ordering_on_save(self): # Be sure we are properly calculating and filling the ordering field From dcd800557f2acb864de4d9c3eed00e58916a4b8b Mon Sep 17 00:00:00 2001 From: Daniel Moldovan Date: Wed, 15 Jan 2020 13:16:44 +0200 Subject: [PATCH 19/21] [THAN-168] - Replace __unicode__ with __str__ --- faq/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/faq/models.py b/faq/models.py index 4f10b3b..55611b2 100644 --- a/faq/models.py +++ b/faq/models.py @@ -112,7 +112,7 @@ class Meta(FAQBase.Meta): verbose_name = _('topic') verbose_name_plural = _('topics') - def __unicode__(self): + def __str__(self): return self.title @models.permalink @@ -144,7 +144,7 @@ class Meta(FAQBase.Meta): verbose_name = _('question') verbose_name_plural = _('questions') - def __unicode__(self): + def __str__(self): return self.question def save(self, *args, **kwargs): From fc3526b27eea28e3a1152eb03f227afa2a8c9d87 Mon Sep 17 00:00:00 2001 From: Daniel Moldovan Date: Wed, 15 Jan 2020 17:37:38 +0200 Subject: [PATCH 20/21] [THAN-168] - Replace unnecessary test --- faq/tests/__init__.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/faq/tests/__init__.py b/faq/tests/__init__.py index 6e0af41..4ce8083 100644 --- a/faq/tests/__init__.py +++ b/faq/tests/__init__.py @@ -62,14 +62,6 @@ def test_manager(self): self.assertTrue(isinstance(Topic.objects, OnSiteManager)) self.assertTrue(isinstance(Question.objects, OnSiteManager)) - def test_unicode(self): - # Ensure that we don't absent-mindedly change what the `__unicode__()` - # method returns. - self.assertEqual(self.topics['new'].__unicode__(), - self.topics['new'].title) - self.assertEqual(self.questions['new1'].__unicode__(), - self.questions['new1'].question) - def test_default_status(self): # Items created without choosing a status should be drafted by default. self.assertEqual(self.topics['new'].status, DRAFTED) From 82fc8f2cfa1a3eab990212348fe6869cc2000795 Mon Sep 17 00:00:00 2001 From: Dan Frakes Date: Tue, 28 Jan 2020 14:24:07 -0500 Subject: [PATCH 21/21] Bump version to 1.0.0 --- faq/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/faq/__init__.py b/faq/__init__.py index c1c63ad..60f1312 100644 --- a/faq/__init__.py +++ b/faq/__init__.py @@ -16,9 +16,9 @@ """ __version_info__ = { - 'major': 0, - 'minor': 9, - 'micro': 1, + 'major': 1, + 'minor': 0, + 'micro': 0, 'releaselevel': 'final', 'serial': 1 }