diff --git a/.travis.yml b/.travis.yml index 57ec6969cc..3602da6bb8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: python env: - - DJANGO_VERSION=1.3.1 - DJANGO_VERSION=1.4 - DJANGO_VERSION=1.5 python: diff --git a/README.rst b/README.rst index 1f9c8346d3..266004a173 100644 --- a/README.rst +++ b/README.rst @@ -73,8 +73,8 @@ Dependencies Mezzanine makes use of as few libraries as possible (apart from a standard Django environment), with the following dependencies: - * `Python`_ 2.5 ... 2.7 - * `Django`_ 1.3 ... 1.4 + * `Python`_ 2.6 / 2.7 + * `Django`_ 1.4 / 1.5 * `Python Imaging Library`_ - for image resizing * `grappelli-safe`_ - admin skin (`Grappelli`_ fork) * `filebrowser-safe`_ - for managing file uploads (`FileBrowser`_ fork) diff --git a/mezzanine/bin/mezzanine_project.py b/mezzanine/bin/mezzanine_project.py index cfac296380..68e1b79b29 100644 --- a/mezzanine/bin/mezzanine_project.py +++ b/mezzanine/bin/mezzanine_project.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -from __future__ import with_statement from distutils.dir_util import copy_tree from optparse import OptionParser import os diff --git a/mezzanine/blog/feeds.py b/mezzanine/blog/feeds.py index e672580f69..d72fe2b4ec 100644 --- a/mezzanine/blog/feeds.py +++ b/mezzanine/blog/feeds.py @@ -1,14 +1,9 @@ -try: - # Django <= 1.3 - from django.contrib.syndication.feeds import Feed -except ImportError: - # Django >= 1.4 - from django.contrib.syndication.views import Feed + +from django.contrib.syndication.views import Feed from django.core.urlresolvers import reverse from django.shortcuts import get_object_or_404 from django.utils.feedgenerator import Atom1Feed from django.utils.html import strip_tags -from django import VERSION from mezzanine.blog.models import BlogPost, BlogCategory from mezzanine.generic.models import Keyword @@ -51,13 +46,6 @@ def __init__(self, *args, **kwargs): self.title = settings.SITE_TITLE self.description = settings.SITE_TAGLINE - def get_feed(self, *args, **kwargs): - # Django 1.3 author/category/tag filtering. - if VERSION < (1, 4) and args[0]: - attr, value = args[0].split("/", 1) - setattr(self, attr, value) - return super(PostsRSS, self).get_feed(*args, **kwargs) - def link(self): return reverse("blog_post_feed", kwargs={"format": "rss"}) diff --git a/mezzanine/blog/models.py b/mezzanine/blog/models.py index 859e7d84a0..be9bb56ba5 100644 --- a/mezzanine/blog/models.py +++ b/mezzanine/blog/models.py @@ -52,16 +52,22 @@ def get_absolute_url(self): }) return (url_name, (), kwargs) - # These methods are wrappers for keyword and category access. - # For Django 1.3, we manually assign keywords and categories - # in the blog_post_list view, since we can't use Django 1.4's - # prefetch_related method. Once we drop support for Django 1.3, - # these can probably be removed. + # These methods are deprectaed wrappers for keyword and category + # access. They existed to support Django 1.3 with prefetch_related + # not existing, which was therefore manually implemented in the + # blog list views. All this is gone now, but the access methods + # still exist for older templates. def category_list(self): + from warnings import warn + warn("blog_post.category_list in templates is deprecated" + "use blog_post.categories.all which are prefetched") return getattr(self, "_categories", self.categories.all()) def keyword_list(self): + from warnings import warn + warn("blog_post.keyword_list in templates is deprecated" + "use the keywords_for template tag, as keywords are prefetched") try: return self._keywords except AttributeError: diff --git a/mezzanine/blog/templates/blog/blog_post_list.html b/mezzanine/blog/templates/blog/blog_post_list.html index f9a756a42c..016d4fb789 100644 --- a/mezzanine/blog/templates/blog/blog_post_list.html +++ b/mezzanine/blog/templates/blog/blog_post_list.html @@ -84,12 +84,14 @@
- {% if blog_post.keyword_list %} + {% keywords_for blog_post as tags %} + {% if tags %} {% trans "Tags" %}: {% spaceless %} - {% for tag in blog_post.keyword_list %} + {% for tag in tags %} {{ tag }} {% endfor %} {% endspaceless %} diff --git a/mezzanine/blog/views.py b/mezzanine/blog/views.py index d2aa5984fb..bc8901f822 100644 --- a/mezzanine/blog/views.py +++ b/mezzanine/blog/views.py @@ -1,15 +1,12 @@ from calendar import month_name -from collections import defaultdict from django.http import Http404 -from django.contrib.contenttypes.models import ContentType from django.shortcuts import get_object_or_404 -from django import VERSION from mezzanine.blog.models import BlogPost, BlogCategory from mezzanine.blog.feeds import PostsRSS, PostsAtom from mezzanine.conf import settings -from mezzanine.generic.models import AssignedKeyword, Keyword +from mezzanine.generic.models import Keyword from mezzanine.utils.views import render, paginate from mezzanine.utils.models import get_user_model @@ -46,41 +43,8 @@ def blog_post_list(request, tag=None, year=None, month=None, username=None, blog_posts = blog_posts.filter(user=author) templates.append(u"blog/blog_post_list_%s.html" % username) - # We want to iterate keywords and categories for each blog post - # without triggering "num posts x 2" queries. - # - # For Django 1.3 we create dicts mapping blog post IDs to lists of - # categories and keywords, and assign these to attributes on each - # blog post. The Blog model then uses accessor methods to retrieve - # these attributes when assigned, which will fall back to the real - # related managers for Django 1.4 and higher, which will already - # have their data retrieved via prefetch_related. - - blog_posts = blog_posts.select_related("user") - if VERSION >= (1, 4): - blog_posts = blog_posts.prefetch_related("categories", - "keywords__keyword") - else: - categories = defaultdict(list) - if blog_posts: - ids = ",".join([str(p.id) for p in blog_posts]) - for cat in BlogCategory.objects.raw( - "SELECT * FROM blog_blogcategory " - "JOIN blog_blogpost_categories " - "ON blog_blogcategory.id = blogcategory_id " - "WHERE blogpost_id IN (%s)" % ids): - categories[cat.blogpost_id].append(cat) - keywords = defaultdict(list) - blogpost_type = ContentType.objects.get(app_label="blog", - model="blogpost") - assigned = AssignedKeyword.objects.filter(blogpost__in=blog_posts, - content_type=blogpost_type).select_related("keyword") - for a in assigned: - keywords[a.object_pk].append(a.keyword) - for i, post in enumerate(blog_posts): - setattr(blog_posts[i], "_categories", categories[post.id]) - setattr(blog_posts[i], "_keywords", keywords[post.id]) - + prefetch = ("categories", "keywords__keyword") + blog_posts = blog_posts.select_related("user").prefetch_related(*prefetch) blog_posts = paginate(blog_posts, request.GET.get("page", 1), settings.BLOG_POST_PER_PAGE, settings.MAX_PAGING_LINKS) @@ -106,22 +70,9 @@ def blog_post_detail(request, slug, year=None, month=None, day=None, def blog_post_feed(request, format, **kwargs): """ - Blog posts feeds - handle difference between Django 1.3 and 1.4 + Blog posts feeds - maps format to the correct feed view. """ - blog_feed_dict = {"rss": PostsRSS, "atom": PostsAtom} try: - blog_feed_dict[format] + return {"rss": PostsRSS, "atom": PostsAtom}[format](**kwargs)(request) except KeyError: raise Http404() - try: - # Django <= 1.3 - from django.contrib.syndication.views import feed - except ImportError: - # Django >= 1.4 - return blog_feed_dict[format](**kwargs)(request) - else: - if len(kwargs) == 1: - # /author/foo/ or /tag/bar/ or /category/baz/ - # gets extracted in get_feed method of feed class. - format += "/%s/%s" % kwargs.items()[0] - return feed(request, format, feed_dict=blog_feed_dict) diff --git a/mezzanine/core/managers.py b/mezzanine/core/managers.py index 26ae5ed910..6e2537d5eb 100644 --- a/mezzanine/core/managers.py +++ b/mezzanine/core/managers.py @@ -6,10 +6,10 @@ from django.db.models.manager import ManagerDescriptor from django.db.models.query import QuerySet from django.contrib.sites.managers import CurrentSiteManager as DjangoCSM +from django.utils.timezone import now from mezzanine.conf import settings from mezzanine.utils.sites import current_site_id -from mezzanine.utils.timezone import now class PublishedManager(Manager): diff --git a/mezzanine/core/models.py b/mezzanine/core/models.py index 3007ad0adc..f76914c9bd 100644 --- a/mezzanine/core/models.py +++ b/mezzanine/core/models.py @@ -6,6 +6,7 @@ from django.template.defaultfilters import truncatewords_html from django.utils.html import strip_tags from django.utils.timesince import timesince +from django.utils.timezone import now from django.utils.translation import ugettext, ugettext_lazy as _ from mezzanine.core.fields import RichTextField @@ -14,7 +15,6 @@ from mezzanine.utils.html import TagCloser from mezzanine.utils.models import base_concrete_model, get_user_model_name from mezzanine.utils.sites import current_site_id -from mezzanine.utils.timezone import now from mezzanine.utils.urls import admin_url, slugify diff --git a/mezzanine/core/templatetags/mezzanine_tags.py b/mezzanine/core/templatetags/mezzanine_tags.py index 5ec59e64bc..bb9ed6eec5 100644 --- a/mezzanine/core/templatetags/mezzanine_tags.py +++ b/mezzanine/core/templatetags/mezzanine_tags.py @@ -1,5 +1,4 @@ -from __future__ import with_statement from hashlib import md5 import os from urllib import urlopen, urlencode, quote, unquote diff --git a/mezzanine/core/tests.py b/mezzanine/core/tests.py index b81a4d0131..94399f8d9f 100644 --- a/mezzanine/core/tests.py +++ b/mezzanine/core/tests.py @@ -1,4 +1,4 @@ -from __future__ import with_statement + import os from shutil import rmtree from urlparse import urlparse diff --git a/mezzanine/core/views.py b/mezzanine/core/views.py index 745c09b3dc..f7ae66e965 100644 --- a/mezzanine/core/views.py +++ b/mezzanine/core/views.py @@ -1,5 +1,4 @@ -from __future__ import with_statement import os from urlparse import urljoin, urlparse diff --git a/mezzanine/forms/forms.py b/mezzanine/forms/forms.py index c596f82ed9..7a7cd4426f 100644 --- a/mezzanine/forms/forms.py +++ b/mezzanine/forms/forms.py @@ -3,18 +3,17 @@ from os.path import join, split from uuid import uuid4 -import django from django import forms from django.forms.extras import SelectDateWidget from django.core.files.storage import FileSystemStorage from django.core.urlresolvers import reverse from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ +from django.utils.timezone import now from mezzanine.conf import settings from mezzanine.forms import fields from mezzanine.forms.models import FormEntry, FieldEntry -from mezzanine.utils.timezone import now fs = FileSystemStorage(location=settings.FORMS_UPLOAD_ROOT) @@ -131,8 +130,8 @@ def __init__(self, form, *args, **kwargs): self.fields[field_key] = field_class(**field_args) if field.field_type == fields.DOB: - now = datetime.now() - years = range(now.year, now.year - 120, -1) + _now = datetime.now() + years = range(_now.year, _now.year - 120, -1) self.fields[field_key].widget.years = years # Add identifying type attr to the field for styling. @@ -171,11 +170,7 @@ def save(self, **kwargs): new = {"entry": entry, "field_id": field.id, "value": value} new_entry_fields.append(FieldEntry(**new)) if new_entry_fields: - if django.VERSION >= (1, 4, 0): - FieldEntry.objects.bulk_create(new_entry_fields) - else: - for field_entry in new_entry_fields: - field_entry.save() + FieldEntry.objects.bulk_create(new_entry_fields) return entry def email_to(self): diff --git a/mezzanine/generic/templatetags/keyword_tags.py b/mezzanine/generic/templatetags/keyword_tags.py index a702090bdb..05eb1b166e 100644 --- a/mezzanine/generic/templatetags/keyword_tags.py +++ b/mezzanine/generic/templatetags/keyword_tags.py @@ -4,7 +4,6 @@ from mezzanine import template from mezzanine.conf import settings -from mezzanine.generic.fields import KeywordsField from mezzanine.generic.models import AssignedKeyword, Keyword @@ -25,14 +24,14 @@ def keywords_for(*args): obj = args[0] if hasattr(obj, "get_content_model"): obj = obj.get_content_model() or obj - # There can only be one ``KeywordsField``, find it. - for field in obj._meta.many_to_many: - if isinstance(field, KeywordsField): - break - else: - return [] - keywords_manager = getattr(obj, field.name) - return [a.keyword for a in keywords_manager.select_related("keyword")] + keywords_name = obj.get_keywordsfield_name() + keywords_queryset = getattr(obj, keywords_name).all() + # Keywords may have been prefetched already. If not, we + # need select_related for the actual keywords. + prefetched = getattr(obj, "_prefetched_objects_cache", {}) + if keywords_name not in prefetched: + keywords_queryset = keywords_queryset.select_related("keyword") + return [assigned.keyword for assigned in keywords_queryset] # Handle a model class. try: diff --git a/mezzanine/twitter/models.py b/mezzanine/twitter/models.py index ce7be6718d..7323e26a3f 100644 --- a/mezzanine/twitter/models.py +++ b/mezzanine/twitter/models.py @@ -8,10 +8,10 @@ from django.utils.html import urlize from django.utils.simplejson import loads from django.utils.translation import ugettext_lazy as _ +from django.utils.timezone import get_default_timezone, make_aware from django.conf import settings from mezzanine.twitter.managers import TweetManager -from mezzanine.utils.timezone import make_aware from mezzanine.twitter import (QUERY_TYPE_CHOICES, QUERY_TYPE_USER, QUERY_TYPE_LIST, QUERY_TYPE_SEARCH) @@ -91,7 +91,7 @@ def run(self): tweet.text = ''.join(chars) d = datetime.strptime(tweet_json["created_at"], date_format) d -= timedelta(seconds=timezone) - tweet.created_at = make_aware(d) + tweet.created_at = make_aware(d, get_default_timezone()) tweet.save() self.interested = False self.save() diff --git a/mezzanine/utils/conf.py b/mezzanine/utils/conf.py index 1a4f3b8a44..e139ffe7ad 100644 --- a/mezzanine/utils/conf.py +++ b/mezzanine/utils/conf.py @@ -177,10 +177,3 @@ def mezzanine_settings(): elif shortname == "mysql": # Required MySQL collation for tests. s["DATABASES"][key]["TEST_COLLATION"] = "utf8_general_ci" - - # Remaining is for Django < 1.4 - from django import VERSION - if VERSION >= (1, 4): - return - s["TEMPLATE_CONTEXT_PROCESSORS"] = list(s["TEMPLATE_CONTEXT_PROCESSORS"]) - remove("TEMPLATE_CONTEXT_PROCESSORS", "django.core.context_processors.tz") diff --git a/mezzanine/utils/docs.py b/mezzanine/utils/docs.py index 35c072d80d..d992a86e1f 100644 --- a/mezzanine/utils/docs.py +++ b/mezzanine/utils/docs.py @@ -3,7 +3,6 @@ documentation is generated. """ -from __future__ import with_statement from datetime import datetime import os.path from shutil import copyfile, move diff --git a/mezzanine/utils/tests.py b/mezzanine/utils/tests.py index d3a3fe8629..8094264daa 100644 --- a/mezzanine/utils/tests.py +++ b/mezzanine/utils/tests.py @@ -1,5 +1,4 @@ -from __future__ import with_statement from _ast import PyCF_ONLY_AST import os from shutil import copyfile, copytree @@ -17,13 +16,6 @@ # Used to version subpackages. "'__version__' imported but unused", - # Backward compatibility for feeds changed in Django 1.4 - "redefinition of unused 'Feed'", - "redefinition of unused 'feed'", - - # Backward compatibility for timezone support - "redefinition of unused 'now'", - # No caching fallback "redefinition of function 'nevercache'", @@ -43,6 +35,9 @@ # Django 1.5 custom user compatibility "redefinition of unused 'get_user_model", + # Deprecated compat timezones for Django 1.3 + "mezzanine/utils/timezone", + ) diff --git a/mezzanine/utils/timezone.py b/mezzanine/utils/timezone.py index 5b67987f07..461453b6f0 100644 --- a/mezzanine/utils/timezone.py +++ b/mezzanine/utils/timezone.py @@ -1,11 +1,7 @@ -# Timezone support with fallback. -try: - from django.utils.timezone import (now, get_default_timezone, - make_aware as django_make_aware) -except ImportError: - from datetime import datetime - now = datetime.now - make_aware = lambda v: v -else: - make_aware = lambda v: django_make_aware(v, get_default_timezone()) +from warnings import warn + +from django.utils.timezone import now, get_default_timezone, make_aware + + +warn("mezzanine.utils.timezone is deprecated, use django.utils.timezone") diff --git a/mezzanine/utils/urls.py b/mezzanine/utils/urls.py index 457a368d98..6801383c1d 100644 --- a/mezzanine/utils/urls.py +++ b/mezzanine/utils/urls.py @@ -2,8 +2,8 @@ import re import unicodedata -from django.core.urlresolvers import resolve, reverse, NoReverseMatch, \ - get_script_prefix +from django.core.urlresolvers import (resolve, reverse, NoReverseMatch, + get_script_prefix) from django.shortcuts import redirect from django.utils.encoding import smart_unicode from django.utils import translation @@ -91,16 +91,8 @@ def path_to_slug(path): a slug that would match a ``Page`` instance's slug. """ from mezzanine.urls import PAGES_SLUG - - # If i18n is disabled Django uses a fake translation object, - # returning None for every path. - try: - lang_code = translation.get_language_from_path(path) - except AttributeError: - lang_code = None - + lang_code = translation.get_language_from_path(path) for prefix in (lang_code, settings.SITE_PREFIX, PAGES_SLUG): if prefix: path = path.replace(prefix, "", 1) - return path.strip("/") or "/"