BACKWARDS-INCOMPATIBLE CHANGE -- Moved flatpages and redirects to standalone apps in django.contrib that are NOT installed by default. See http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges for full migration information.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@1166 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2005-11-11 04:45:05 +00:00
parent a11a1d5e16
commit 1b035c35d9
21 changed files with 310 additions and 123 deletions

View File

@ -135,9 +135,6 @@ ALLOWED_INCLUDE_ROOTS = ()
# is an admin. # is an admin.
ADMIN_FOR = () ADMIN_FOR = ()
# Whether to check the flat-pages table as a last resort for all 404 errors.
USE_FLAT_PAGES = True
# 404s that may be ignored. # 404s that may be ignored.
IGNORABLE_404_STARTS = ('/cgi-bin/', '/_vti_bin', '/_vti_inf') IGNORABLE_404_STARTS = ('/cgi-bin/', '/_vti_bin', '/_vti_inf')
IGNORABLE_404_ENDS = ('mail.pl', 'mailform.pl', 'mail.cgi', 'mailform.cgi', 'favicon.ico', '.php') IGNORABLE_404_ENDS = ('mail.pl', 'mailform.pl', 'mail.cgi', 'mailform.cgi', 'favicon.ico', '.php')

View File

View File

@ -0,0 +1,15 @@
from django.contrib.flatpages.views import flatpage
from django.conf.settings import DEBUG
class FlatpageFallbackMiddleware:
def process_response(self, request, response):
if response.status_code != 404:
return response # No need to check for a flatpage for non-404 responses.
try:
return flatpage(request, request.path)
# Return the original response if any errors happened. Because this
# is a middleware, we can't assume the errors will be caught elsewhere.
except:
if DEBUG:
raise
return response

View File

@ -0,0 +1 @@
__all__ = ['flatpages']

View File

@ -0,0 +1,33 @@
from django.core import meta, validators
from django.models.core import Site
from django.utils.translation import gettext_lazy as _
class FlatPage(meta.Model):
url = meta.CharField(_('URL'), maxlength=100, validator_list=[validators.isAlphaNumericURL],
help_text=_("Example: '/about/contact/'. Make sure to have leading and trailing slashes."))
title = meta.CharField(_('title'), maxlength=200)
content = meta.TextField(_('content'))
enable_comments = meta.BooleanField(_('enable comments'))
template_name = meta.CharField(_('template name'), maxlength=70, blank=True,
help_text=_("Example: 'flatpages/contact_page'. If this isn't provided, the system will use 'flatpages/default'."))
registration_required = meta.BooleanField(_('registration required'), help_text=_("If this is checked, only logged-in users will be able to view the page."))
sites = meta.ManyToManyField(Site)
class META:
db_table = 'django_flatpages'
verbose_name = _('flat page')
verbose_name_plural = _('flat pages')
ordering = ('url',)
admin = meta.Admin(
fields = (
(None, {'fields': ('url', 'title', 'content', 'sites')}),
('Advanced options', {'classes': 'collapse', 'fields': ('enable_comments', 'registration_required', 'template_name')}),
),
list_filter = ('sites',),
search_fields = ('url', 'title'),
)
def __repr__(self):
return "%s -- %s" % (self.url, self.title)
def get_absolute_url(self):
return self.url

View File

@ -1,5 +1,5 @@
from django.conf.urls.defaults import * from django.conf.urls.defaults import *
urlpatterns = patterns('django.views', urlpatterns = patterns('django.views',
(r'^(?P<url>.*)$', 'core.flatfiles.flat_file'), (r'^(?P<url>.*)$', 'django.contrib.flatpages.views.flatpage'),
) )

View File

@ -1,28 +1,28 @@
from django.core import template_loader from django.core import template_loader
from django.core.extensions import get_object_or_404, DjangoContext from django.core.extensions import get_object_or_404, DjangoContext
from django.models.core import flatfiles from django.models.flatpages import flatpages
from django.utils.httpwrappers import HttpResponse from django.utils.httpwrappers import HttpResponse
from django.conf.settings import SITE_ID from django.conf.settings import SITE_ID
DEFAULT_TEMPLATE = 'flatfiles/default' DEFAULT_TEMPLATE = 'flatpages/default'
def flat_file(request, url): def flatpage(request, url):
""" """
Flat file view Flat page view.
Models: `core.flatfiles` Models: `flatpages.flatpages`
Templates: Uses the template defined by the ``template_name`` field, Templates: Uses the template defined by the ``template_name`` field,
or `flatfiles/default` if template_name is not defined. or `flatpages/default` if template_name is not defined.
Context: Context:
flatfile flatpage
`flatfiles.flatfiles` object `flatpages.flatpages` object
""" """
if not url.startswith('/'): if not url.startswith('/'):
url = "/" + url url = "/" + url
f = get_object_or_404(flatfiles, url__exact=url, sites__id__exact=SITE_ID) f = get_object_or_404(flatpages, url__exact=url, sites__id__exact=SITE_ID)
# If registration is required for accessing this page, and the user isn't # If registration is required for accessing this page, and the user isn't
# logged in, redirect to the login page. # logged in, redirect to the login page.
if request.user.is_anonymous() and f.registration_required: if f.registration_required and request.user.is_anonymous():
from django.views.auth.login import redirect_to_login from django.views.auth.login import redirect_to_login
return redirect_to_login(request.path) return redirect_to_login(request.path)
if f.template_name: if f.template_name:
@ -30,6 +30,6 @@ def flat_file(request, url):
else: else:
t = template_loader.get_template(DEFAULT_TEMPLATE) t = template_loader.get_template(DEFAULT_TEMPLATE)
c = DjangoContext(request, { c = DjangoContext(request, {
'flatfile': f, 'flatpage': f,
}) })
return HttpResponse(t.render(c)) return HttpResponse(t.render(c))

View File

View File

@ -0,0 +1,27 @@
from django.models.redirects import redirects
from django.utils import httpwrappers
from django.conf.settings import APPEND_SLASH, SITE_ID
class RedirectFallbackMiddleware:
def process_response(self, request, response):
if response.status_code != 404:
return response # No need to check for a redirect for non-404 responses.
path = request.get_full_path()
try:
r = redirects.get_object(site__id__exact=SITE_ID, old_path__exact=path)
except redirects.RedirectDoesNotExist:
r = None
if r is None and APPEND_SLASH:
# Try removing the trailing slash.
try:
r = redirects.get_object(site__id__exact=SITE_ID,
old_path__exact=path[:path.rfind('/')]+path[path.rfind('/')+1:])
except redirects.RedirectDoesNotExist:
pass
if r is not None:
if r == '':
return httpwrappers.HttpResponseGone()
return httpwrappers.HttpResponseRedirect(r.new_path)
# No redirect was found. Return the response.
return response

View File

@ -0,0 +1 @@
__all__ = ['redirects']

View File

@ -0,0 +1,23 @@
from django.core import meta
from django.models.core import Site
from django.utils.translation import gettext_lazy as _
class Redirect(meta.Model):
site = meta.ForeignKey(Site, radio_admin=meta.VERTICAL)
old_path = meta.CharField(_('redirect from'), maxlength=200, db_index=True,
help_text=_("This should be an absolute path, excluding the domain name. Example: '/events/search/'."))
new_path = meta.CharField(_('redirect to'), maxlength=200, blank=True,
help_text=_("This can be either an absolute path (as above) or a full URL starting with 'http://'."))
class META:
verbose_name = _('redirect')
verbose_name_plural = _('redirects')
db_table = 'django_redirects'
unique_together=(('site', 'old_path'),)
ordering = ('old_path',)
admin = meta.Admin(
list_filter = ('site',),
search_fields = ('old_path', 'new_path'),
)
def __repr__(self):
return "%s ---> %s" % (self.old_path, self.new_path)

View File

@ -1,8 +1,6 @@
from django.conf import settings from django.conf import settings
from django.core import exceptions
from django.utils import httpwrappers from django.utils import httpwrappers
from django.core.mail import mail_managers from django.core.mail import mail_managers
from django.views.core.flatfiles import flat_file
import md5, os import md5, os
class CommonMiddleware: class CommonMiddleware:
@ -17,9 +15,6 @@ class CommonMiddleware:
- ETags: If the USE_ETAGS setting is set, ETags will be calculated from - ETags: If the USE_ETAGS setting is set, ETags will be calculated from
the entire page content and Not Modified responses will be returned the entire page content and Not Modified responses will be returned
appropriately. appropriately.
- Flat files: For 404 responses, a flat file matching the given path
will be looked up and used if found.
""" """
def process_request(self, request): def process_request(self, request):
@ -55,12 +50,6 @@ class CommonMiddleware:
def process_response(self, request, response): def process_response(self, request, response):
"Check for a flat page (for 404s) and calculate the Etag, if needed." "Check for a flat page (for 404s) and calculate the Etag, if needed."
if response.status_code == 404: if response.status_code == 404:
if settings.USE_FLAT_PAGES:
try:
return flat_file(request, request.path)
except exceptions.Http404:
pass
if settings.SEND_BROKEN_LINK_EMAILS: if settings.SEND_BROKEN_LINK_EMAILS:
# If the referrer was from an internal link or a non-search-engine site, # If the referrer was from an internal link or a non-search-engine site,
# send a note to the managers. # send a note to the managers.

View File

@ -1,6 +1,6 @@
import base64, md5, random, sys import base64, md5, random, sys
import cPickle as pickle import cPickle as pickle
from django.core import meta, validators from django.core import meta
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
class Site(meta.Model): class Site(meta.Model):
@ -63,56 +63,6 @@ class ContentType(meta.Model):
""" """
return self.get_model_module().get_object(**kwargs) return self.get_model_module().get_object(**kwargs)
class Redirect(meta.Model):
site = meta.ForeignKey(Site, radio_admin=meta.VERTICAL)
old_path = meta.CharField(_('redirect from'), maxlength=200, db_index=True,
help_text=_("This should be an absolute path, excluding the domain name. Example: '/events/search/'."))
new_path = meta.CharField(_('redirect to'), maxlength=200, blank=True,
help_text=_("This can be either an absolute path (as above) or a full URL starting with 'http://'."))
class META:
verbose_name = _('redirect')
verbose_name_plural = _('redirects')
db_table = 'redirects'
unique_together=(('site', 'old_path'),)
ordering = ('old_path',)
admin = meta.Admin(
list_filter = ('site',),
search_fields = ('old_path', 'new_path'),
)
def __repr__(self):
return "%s ---> %s" % (self.old_path, self.new_path)
class FlatFile(meta.Model):
url = meta.CharField(_('URL'), maxlength=100, validator_list=[validators.isAlphaNumericURL],
help_text=_("Example: '/about/contact/'. Make sure to have leading and trailing slashes."))
title = meta.CharField(_('title'), maxlength=200)
content = meta.TextField(_('content'))
enable_comments = meta.BooleanField(_('enable comments'))
template_name = meta.CharField(_('template name'), maxlength=70, blank=True,
help_text=_("Example: 'flatfiles/contact_page'. If this isn't provided, the system will use 'flatfiles/default'."))
registration_required = meta.BooleanField(_('registration required'), help_text=_("If this is checked, only logged-in users will be able to view the page."))
sites = meta.ManyToManyField(Site)
class META:
db_table = 'flatfiles'
verbose_name = _('flat page')
verbose_name_plural = _('flat pages')
ordering = ('url',)
admin = meta.Admin(
fields = (
(None, {'fields': ('url', 'title', 'content', 'sites')}),
('Advanced options', {'classes': 'collapse', 'fields': ('enable_comments', 'registration_required', 'template_name')}),
),
list_filter = ('sites',),
search_fields = ('url', 'title'),
)
def __repr__(self):
return "%s -- %s" % (self.url, self.title)
def get_absolute_url(self):
return self.url
class Session(meta.Model): class Session(meta.Model):
session_key = meta.CharField(_('session key'), maxlength=40, primary_key=True) session_key = meta.CharField(_('session key'), maxlength=40, primary_key=True)
session_data = meta.TextField(_('session data')) session_data = meta.TextField(_('session data'))

View File

@ -4,7 +4,7 @@ from django.models.core import sites, contenttypes
from django.utils import httpwrappers from django.utils import httpwrappers
def shortcut(request, content_type_id, object_id): def shortcut(request, content_type_id, object_id):
"""Redirect to an object's page based on a content-type ID and an object ID""" "Redirect to an object's page based on a content-type ID and an object ID."
# Look up the object, making sure it's got a get_absolute_url() function. # Look up the object, making sure it's got a get_absolute_url() function.
try: try:
content_type = contenttypes.get_object(pk=content_type_id) content_type = contenttypes.get_object(pk=content_type_id)
@ -16,8 +16,8 @@ def shortcut(request, content_type_id, object_id):
except AttributeError: except AttributeError:
raise Http404, "%s objects don't have get_absolute_url() methods" % content_type.name raise Http404, "%s objects don't have get_absolute_url() methods" % content_type.name
# Try to figure out the object's domain so we can do a cross-site redirect # Try to figure out the object's domain, so we can do a cross-site redirect
# if necessary # if necessary.
# If the object actually defines a domain, we're done. # If the object actually defines a domain, we're done.
if absurl.startswith('http://'): if absurl.startswith('http://'):
@ -55,34 +55,15 @@ def page_not_found(request):
Templates: `404` Templates: `404`
Context: None Context: None
""" """
from django.models.core import redirects
from django.conf.settings import APPEND_SLASH, SITE_ID
path = request.get_full_path()
try:
r = redirects.get_object(site__id__exact=SITE_ID, old_path__exact=path)
except redirects.RedirectDoesNotExist:
r = None
if r is None and APPEND_SLASH:
# Try removing the trailing slash.
try:
r = redirects.get_object(site__id__exact=SITE_ID, old_path__exact=path[:path.rfind('/')]+path[path.rfind('/')+1:])
except redirects.RedirectDoesNotExist:
pass
if r is not None:
if r == '':
return httpwrappers.HttpResponseGone()
return httpwrappers.HttpResponseRedirect(r.new_path)
t = loader.get_template('404') t = loader.get_template('404')
c = Context() return httpwrappers.HttpResponseNotFound(t.render(Context()))
return httpwrappers.HttpResponseNotFound(t.render(c))
def server_error(request): def server_error(request):
""" """
500 Error handler 500 error handler.
Templates: `500` Templates: `500`
Context: None Context: None
""" """
t = loader.get_template('500') t = loader.get_template('500')
c = Context() return httpwrappers.HttpResponseServerError(t.render(Context()))
return httpwrappers.HttpResponseServerError(t.render(c))

114
docs/flatpages.txt Normal file
View File

@ -0,0 +1,114 @@
=================
The flatpages app
=================
Django comes with an optional "flatpages" application. It lets you store simple
"flat" HTML content in a database and handles the management for you.
A flatpage is a simple object with a URL, title and content. Use it for
one-off, special-case pages, such as "About" or "Privacy Policy" pages, that
you want to store in a database but for which you don't want to develop a
custom Django application.
A flatpage can use a custom template or a default, systemwide flatpage
template. It can be associated with one, or multiple, sites.
Here are some examples of flatpages on Django-powered sites:
* http://www.chicagocrime.org/about/
* http://www.lawrence.com/about/contact/
Installation
============
To install the flatpages app, follow these two steps:
1. Add ``"django.contrib.flatpages"`` to your INSTALLED_APPS_ setting.
2. Add ``"django.contrib.flatpages.middleware.FlatpageFallbackMiddleware"``
to your MIDDLEWARE_CLASSES_ setting.
3. Run the command ``django-admin.py install flatpages``.
.. _INSTALLED_APPS: http://www.djangoproject.com/documentation/settings/#installed-apps
.. _MIDDLEWARE_CLASSES: http://www.djangoproject.com/documentation/settings/#middleware-classes
How it works
============
``django-admin.py install flatpages`` creates two tables in your database:
``django_flatpages`` and ``django_flatpages_sites``. ``django_flatpages`` is a
simple lookup table that essentially maps a URL to a title and bunch of text
content. ``django_flatpages_sites`` associates a flatpage with a site.
The ``FlatpageFallbackMiddleware`` does all of the work. Each time any Django
application raises a 404 error, this middleware checks the flatpages database
for the requested URL as a last resort. Specifically, it checks for a flatpage
with the given URL with a site ID that corresponds to the SITE_ID_ setting.
If it finds a match, it follows this algorithm:
* If the flatpage has a custom template, it loads that template. Otherwise,
it loads the template ``flatpages/default``.
* It passes that template a single context variable, ``flatpage``, which is
the flatpage object. It uses DjangoContext_ in rendering the template.
If it doesn't find a match, the request continues to be processed as usual.
The middleware only gets activated for 404s -- not for 500s or responses of any
other status code.
Note that the order of ``MIDDLEWARE_CLASSES`` matters. Generally, you can put
``FlatpageFallbackMiddleware`` at the end of the list, because it's a last
resort.
For more on middleware, read the `middleware docs`_.
.. _SITE_ID: http://www.djangoproject.com/documentation/settings/#site-id
.. _DjangoContext: http://www.djangoproject.com/documentation/templates_python/#subclassing-context-djangocontext
.. _middleware docs: http://www.djangoproject.com/documentation/middleware/
How to add, change and delete flatpages
=======================================
Via the admin interface
-----------------------
If you've activated the automatic Django admin interface, you should see a
"Flatpages" section on the admin index page. Edit flatpages as you edit any
other object in the system.
Via the Python API
------------------
Flatpages are represented by a standard `Django model`_, which lives in
`django/contrib/flatpages/models/flatpages.py`_. You can access flatpage
objects via the `Django database API`_.
.. _Django model: http://www.djangoproject.com/documentation/model_api/
.. _django/contrib/flatpages/models/flatpages.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/flatpages/models/flatpages.py
.. _Django database API: http://www.djangoproject.com/documentation/db_api/
Flatpage templates
==================
By default, flatpages are rendered via the template ``flatpages/default``, but
you can override that for a particular flatpage.
Creating the ``flatpages/default`` template is your responsibility; in your
template directory, just create a ``flatpages`` directory containing a file
``default.html``.
Flatpage templates are passed a single context variable, ``flatpage``, which is
the flatpage object.
Here's a sample ``flatpages/default`` template::
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<head>
<title>{{ flatpage.title }}</title>
</head>
<body>
{{ flatpage.content }}
</body>
</html>

View File

@ -69,10 +69,7 @@ following names:
* ``sites`` * ``sites``
* ``packages`` * ``packages``
* ``content_types`` * ``content_types``
* ``redirects``
* ``flatfiles``
* ``core_sessions`` * ``core_sessions``
* ``flatfiles_sites``
* ``auth_permissions`` * ``auth_permissions``
* ``auth_groups`` * ``auth_groups``
* ``auth_users`` * ``auth_users``

View File

@ -72,10 +72,6 @@ Adds a few conveniences for perfectionists:
MD5-hashing the page content, and it'll take care of sending MD5-hashing the page content, and it'll take care of sending
``Not Modified`` responses, if appropriate. ``Not Modified`` responses, if appropriate.
* Handles flat pages. Every time Django encounters a 404 -- either within
a view or as a result of no URLconfs matching -- it will check the
database of flat pages based on the current URL.
django.middleware.doc.XViewMiddleware django.middleware.doc.XViewMiddleware
------------------------------------- -------------------------------------

View File

@ -831,7 +831,7 @@ object, which takes the following parameters. All are optional.
"click to expand" link. Fieldsets with the ``wide`` style will be "click to expand" link. Fieldsets with the ``wide`` style will be
given extra horizontal space. given extra horizontal space.
For example (taken from the ``core.flatfiles`` model):: For example (taken from the ``django.contrib.flatpages`` model)::
fields = ( fields = (
(None, { (None, {

72
docs/redirects.txt Normal file
View File

@ -0,0 +1,72 @@
=================
The redirects app
=================
Django comes with an optional redirects application. It lets you store simple
redirects in a database and handles the redirecting for you.
Installation
============
To install the redirects app, follow these two steps:
1. Add ``"django.contrib.redirects"`` to your INSTALLED_APPS_ setting.
2. Add ``"django.contrib.redirects.middleware.RedirectFallbackMiddleware"``
to your MIDDLEWARE_CLASSES_ setting.
3. Run the command ``django-admin.py install redirects``.
.. _INSTALLED_APPS: http://www.djangoproject.com/documentation/settings/#installed-apps
.. _MIDDLEWARE_CLASSES: http://www.djangoproject.com/documentation/settings/#middleware-classes
How it works
============
``django-admin.py install redirects`` creates a ``django_redirects`` table in
your database. This is a simple lookup table with ``site_id``, ``old_path`` and
``new_path`` fields.
The ``RedirectFallbackMiddleware`` does all of the work. Each time any Django
application raises a 404 error, this middleware checks the redirects database
for the requested URL as a last resort. Specifically, it checks for a redirect
with the given ``old_path`` with a site ID that corresponds to the SITE_ID_
setting.
* If it finds a match, and ``new_path`` is not empty, it redirects to
``new_path``.
* If it finds a match, and ``new_path`` is empty, it sends a 410 ("Gone")
HTTP header and empty (content-less) response.
* If it doesn't find a match, the request continues to be processed as
usual.
The middleware only gets activated for 404s -- not for 500s or responses of any
other status code.
Note that the order of ``MIDDLEWARE_CLASSES`` matters. Generally, you can put
``RedirectFallbackMiddleware`` at the end of the list, because it's a last
resort.
For more on middleware, read the `middleware docs`_.
.. _SITE_ID: http://www.djangoproject.com/documentation/settings/#site-id
.. _middleware docs: http://www.djangoproject.com/documentation/middleware/
How to add, change and delete redirects
=======================================
Via the admin interface
-----------------------
If you've activated the automatic Django admin interface, you should see a
"Redirects" section on the admin index page. Edit redirects as you edit any
other object in the system.
Via the Python API
------------------
Redirects are represented by a standard `Django model`_, which lives in
`django/contrib/redirects/models/redirects.py`_. You can access redirect
objects via the `Django database API`_.
.. _Django model: http://www.djangoproject.com/documentation/model_api/
.. _django/contrib/redirects/models/redirects.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/redirects/models/redirects.py
.. _Django database API: http://www.djangoproject.com/documentation/db_api/

View File

@ -562,14 +562,6 @@ A boolean that specifies whether to output the "Etag" header. This saves
bandwidth but slows down performance. This is only used if ``CommonMiddleware`` bandwidth but slows down performance. This is only used if ``CommonMiddleware``
is installed (see the `middleware docs`_). is installed (see the `middleware docs`_).
USE_FLAT_PAGES
--------------
Default: ``True``
Whether to check the flat-pages table as a last resort for all 404 errors. This
is only used if ``CommonMiddleware`` is installed (see the `middleware docs`_).
.. _cache docs: http://www.djangoproject.com/documentation/cache/ .. _cache docs: http://www.djangoproject.com/documentation/cache/
.. _middleware docs: http://www.djangoproject.com/documentation/middleware/ .. _middleware docs: http://www.djangoproject.com/documentation/middleware/
.. _session docs: http://www.djangoproject.com/documentation/sessions/ .. _session docs: http://www.djangoproject.com/documentation/sessions/

View File

@ -56,7 +56,6 @@ module that will be used for the entire site. Here's the URLconf for the
(r'^documentation/', include('django_website.apps.docs.urls.docs')), (r'^documentation/', include('django_website.apps.docs.urls.docs')),
(r'^comments/', include('django.contrib.comments.urls.comments')), (r'^comments/', include('django.contrib.comments.urls.comments')),
(r'^rss/', include('django.conf.urls.rss')), (r'^rss/', include('django.conf.urls.rss')),
(r'', include('django.conf.urls.flatfiles')),
) )
Note that an included URLconf receives any captured parameters from parent Note that an included URLconf receives any captured parameters from parent