Fixed #163 -- Added 'pk' database API option, which is a shorthand for (primary_key)__exact

git-svn-id: http://code.djangoproject.com/svn/django/trunk@316 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2005-07-26 16:11:43 +00:00
parent f14c98e44c
commit 786c750c40
14 changed files with 68 additions and 42 deletions

View File

@ -78,7 +78,7 @@ class Comment(meta.Model):
""" """
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
try: try:
return self.get_content_type().get_object_for_this_type(id__exact=self.object_id) return self.get_content_type().get_object_for_this_type(pk=self.object_id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
return None return None
@ -193,7 +193,7 @@ class FreeComment(meta.Model):
""" """
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
try: try:
return self.get_content_type().get_object_for_this_type(id__exact=self.object_id) return self.get_content_type().get_object_for_this_type(pk=self.object_id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
return None return None

View File

@ -80,7 +80,7 @@ class CommentFormNode(template.Node):
# We only have to do this validation if obj_id_lookup_var is provided, # We only have to do this validation if obj_id_lookup_var is provided,
# because do_comment_form() validates hard-coded object IDs. # because do_comment_form() validates hard-coded object IDs.
try: try:
self.content_type.get_object_for_this_type(id__exact=self.obj_id) self.content_type.get_object_for_this_type(pk=self.obj_id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
context['display_form'] = False context['display_form'] = False
else: else:
@ -203,7 +203,7 @@ class DoCommentForm:
if tokens[3].isdigit(): if tokens[3].isdigit():
obj_id = tokens[3] obj_id = tokens[3]
try: # ensure the object ID is valid try: # ensure the object ID is valid
content_type.get_object_for_this_type(id__exact=obj_id) content_type.get_object_for_this_type(pk=obj_id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
raise template.TemplateSyntaxError, "'%s' tag refers to %s object with ID %s, which doesn't exist" % (self.tag_name, content_type.name, obj_id) raise template.TemplateSyntaxError, "'%s' tag refers to %s object with ID %s, which doesn't exist" % (self.tag_name, content_type.name, obj_id)
else: else:
@ -283,7 +283,7 @@ class DoCommentCount:
if tokens[3].isdigit(): if tokens[3].isdigit():
obj_id = tokens[3] obj_id = tokens[3]
try: # ensure the object ID is valid try: # ensure the object ID is valid
content_type.get_object_for_this_type(id__exact=obj_id) content_type.get_object_for_this_type(pk=obj_id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
raise template.TemplateSyntaxError, "'%s' tag refers to %s object with ID %s, which doesn't exist" % (self.tag_name, content_type.name, obj_id) raise template.TemplateSyntaxError, "'%s' tag refers to %s object with ID %s, which doesn't exist" % (self.tag_name, content_type.name, obj_id)
else: else:
@ -338,7 +338,7 @@ class DoGetCommentList:
if tokens[3].isdigit(): if tokens[3].isdigit():
obj_id = tokens[3] obj_id = tokens[3]
try: # ensure the object ID is valid try: # ensure the object ID is valid
content_type.get_object_for_this_type(id__exact=obj_id) content_type.get_object_for_this_type(pk=obj_id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
raise template.TemplateSyntaxError, "'%s' tag refers to %s object with ID %s, which doesn't exist" % (self.tag_name, content_type.name, obj_id) raise template.TemplateSyntaxError, "'%s' tag refers to %s object with ID %s, which doesn't exist" % (self.tag_name, content_type.name, obj_id)
else: else:

View File

@ -197,7 +197,7 @@ def post_comment(request):
rating_range, rating_choices = [], [] rating_range, rating_choices = [], []
content_type_id, object_id = target.split(':') # target is something like '52:5157' content_type_id, object_id = target.split(':') # target is something like '52:5157'
try: try:
obj = contenttypes.get_object(id__exact=content_type_id).get_object_for_this_type(id__exact=object_id) obj = contenttypes.get_object(pk=content_type_id).get_object_for_this_type(pk=object_id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
raise Http404, "The comment form had an invalid 'target' parameter -- the object ID was invalid" raise Http404, "The comment form had an invalid 'target' parameter -- the object ID was invalid"
option_list = options.split(',') # options is something like 'pa,ra' option_list = options.split(',') # options is something like 'pa,ra'
@ -284,9 +284,9 @@ def post_free_comment(request):
if comments.get_security_hash(options, '', '', target) != security_hash: if comments.get_security_hash(options, '', '', target) != security_hash:
raise Http404, "Somebody tampered with the comment form (security violation)" raise Http404, "Somebody tampered with the comment form (security violation)"
content_type_id, object_id = target.split(':') # target is something like '52:5157' content_type_id, object_id = target.split(':') # target is something like '52:5157'
content_type = contenttypes.get_object(id__exact=content_type_id) content_type = contenttypes.get_object(pk=content_type_id)
try: try:
obj = content_type.get_object_for_this_type(id__exact=object_id) obj = content_type.get_object_for_this_type(pk=object_id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
raise Http404, "The comment form had an invalid 'target' parameter -- the object ID was invalid" raise Http404, "The comment form had an invalid 'target' parameter -- the object ID was invalid"
option_list = options.split(',') option_list = options.split(',')
@ -336,8 +336,8 @@ def comment_was_posted(request):
if request.GET.has_key('c'): if request.GET.has_key('c'):
content_type_id, object_id = request.GET['c'].split(':') content_type_id, object_id = request.GET['c'].split(':')
try: try:
content_type = contenttypes.get_object(id__exact=content_type_id) content_type = contenttypes.get_object(pk=content_type_id)
obj = content_type.get_object_for_this_type(id__exact=object_id) obj = content_type.get_object_for_this_type(pk=object_id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
pass pass
t = template_loader.get_template('comments/posted') t = template_loader.get_template('comments/posted')

View File

@ -19,14 +19,14 @@ def vote(request, comment_id, vote):
if request.user.is_anonymous(): if request.user.is_anonymous():
raise Http404, "Anonymous users cannot vote" raise Http404, "Anonymous users cannot vote"
try: try:
comment = comments.get_object(id__exact=comment_id) comment = comments.get_object(pk=comment_id)
except comments.CommentDoesNotExist: except comments.CommentDoesNotExist:
raise Http404, "Invalid comment ID" raise Http404, "Invalid comment ID"
if comment.user_id == request.user.id: if comment.user_id == request.user.id:
raise Http404, "No voting for yourself" raise Http404, "No voting for yourself"
karma.vote(request.user.id, comment_id, rating) karma.vote(request.user.id, comment_id, rating)
# Reload comment to ensure we have up to date karma count # Reload comment to ensure we have up to date karma count
comment = comments.get_object(id__exact=comment_id) comment = comments.get_object(pk=comment_id)
t = template_loader.get_template('comments/karma_vote_accepted') t = template_loader.get_template('comments/karma_vote_accepted')
c = Context(request, { c = Context(request, {
'comment': comment 'comment': comment

View File

@ -16,7 +16,7 @@ def flag(request, comment_id):
the flagged `comments.comments` object the flagged `comments.comments` object
""" """
try: try:
comment = comments.get_object(id__exact=comment_id, site_id__exact=SITE_ID) comment = comments.get_object(pk=comment_id, site_id__exact=SITE_ID)
except comments.CommentDoesNotExist: except comments.CommentDoesNotExist:
raise Http404 raise Http404
if request.POST: if request.POST:
@ -31,7 +31,7 @@ flag = login_required(flag)
def flag_done(request, comment_id): def flag_done(request, comment_id):
try: try:
comment = comments.get_object(id__exact=comment_id, site_id__exact=SITE_ID) comment = comments.get_object(pk=comment_id, site_id__exact=SITE_ID)
except comments.CommentDoesNotExist: except comments.CommentDoesNotExist:
raise Http404 raise Http404
t = template_loader.get_template('comments/flag_done') t = template_loader.get_template('comments/flag_done')
@ -50,7 +50,7 @@ def delete(request, comment_id):
the flagged `comments.comments` object the flagged `comments.comments` object
""" """
try: try:
comment = comments.get_object(id__exact=comment_id, site_id__exact=SITE_ID) comment = comments.get_object(pk=comment_id, site_id__exact=SITE_ID)
except comments.CommentDoesNotExist: except comments.CommentDoesNotExist:
raise Http404 raise Http404
if not comments.user_is_moderator(request.user): if not comments.user_is_moderator(request.user):
@ -72,7 +72,7 @@ delete = login_required(delete)
def delete_done(request, comment_id): def delete_done(request, comment_id):
try: try:
comment = comments.get_object(id__exact=comment_id, site_id__exact=SITE_ID) comment = comments.get_object(pk=comment_id, site_id__exact=SITE_ID)
except comments.CommentDoesNotExist: except comments.CommentDoesNotExist:
raise Http404 raise Http404
t = template_loader.get_template('comments/delete_done') t = template_loader.get_template('comments/delete_done')

View File

@ -1146,6 +1146,9 @@ def _parse_lookup(kwarg_items, opts, table_count=0):
params.extend(params2) params.extend(params2)
continue continue
lookup_list = kwarg.split(LOOKUP_SEPARATOR) lookup_list = kwarg.split(LOOKUP_SEPARATOR)
# pk="value" is shorthand for (primary key)__exact="value"
if lookup_list[-1] == 'pk':
lookup_list = lookup_list[:-1] + [opts.pk.name, 'exact']
if len(lookup_list) == 1: if len(lookup_list) == 1:
_throw_bad_kwarg_error(kwarg) _throw_bad_kwarg_error(kwarg)
lookup_type = lookup_list.pop() lookup_type = lookup_list.pop()

View File

@ -222,7 +222,7 @@ class Session(meta.Model):
"Sets the necessary cookie in the given HttpResponse object, also updates last login time for user." "Sets the necessary cookie in the given HttpResponse object, also updates last login time for user."
from django.models.auth import users from django.models.auth import users
from django.conf.settings import REGISTRATION_COOKIE_DOMAIN from django.conf.settings import REGISTRATION_COOKIE_DOMAIN
user = users.get_object(id__exact=user_id) user = users.get_object(pk=user_id)
user.last_login = datetime.datetime.now() user.last_login = datetime.datetime.now()
user.save() user.save()
session = create_session(user_id) session = create_session(user_id)
@ -274,7 +274,7 @@ class LogEntry(meta.Model):
def get_edited_object(self): def get_edited_object(self):
"Returns the edited object represented by this log entry" "Returns the edited object represented by this log entry"
return self.get_content_type().get_object_for_this_type(id__exact=self.object_id) return self.get_content_type().get_object_for_this_type(pk=self.object_id)
def get_admin_url(self): def get_admin_url(self):
""" """

View File

@ -14,7 +14,7 @@ class Site(meta.Model):
def _module_get_current(): def _module_get_current():
"Returns the current site, according to the SITE_ID constant." "Returns the current site, according to the SITE_ID constant."
from django.conf.settings import SITE_ID from django.conf.settings import SITE_ID
return get_object(id__exact=SITE_ID) return get_object(pk=SITE_ID)
class Package(meta.Model): class Package(meta.Model):
db_table = 'packages' db_table = 'packages'

View File

@ -1,6 +1,3 @@
import os
import re
import inspect
from django.core import meta from django.core import meta
from django import templatetags from django import templatetags
from django.conf import settings from django.conf import settings
@ -14,6 +11,7 @@ try:
from django.parts.admin import doc from django.parts.admin import doc
except ImportError: except ImportError:
doc = None doc = None
import inspect, os, re
# Exclude methods starting with these strings from documentation # Exclude methods starting with these strings from documentation
MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_') MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_')
@ -128,7 +126,7 @@ def view_index(request):
'module' : func.__module__, 'module' : func.__module__,
'title' : title, 'title' : title,
'site_id': settings_mod.SITE_ID, 'site_id': settings_mod.SITE_ID,
'site' : sites.get_object(id__exact=settings_mod.SITE_ID), 'site' : sites.get_object(pk=settings_mod.SITE_ID),
'url' : simplify_regex(regex), 'url' : simplify_regex(regex),
}) })
t = template_loader.get_template('doc/view_index') t = template_loader.get_template('doc/view_index')

View File

@ -7,8 +7,8 @@ from django.utils import httpwrappers
def shortcut(request, content_type_id, object_id): def shortcut(request, content_type_id, object_id):
from django.models.core import contenttypes from django.models.core import contenttypes
try: try:
content_type = contenttypes.get_object(id__exact=content_type_id) content_type = contenttypes.get_object(pk=content_type_id)
obj = content_type.get_object_for_this_type(id__exact=object_id) obj = content_type.get_object_for_this_type(pk=object_id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
raise Http404, "Content type %s object %s doesn't exist" % (content_type_id, object_id) raise Http404, "Content type %s object %s doesn't exist" % (content_type_id, object_id)
if not hasattr(obj, 'get_absolute_url'): if not hasattr(obj, 'get_absolute_url'):

View File

@ -97,6 +97,19 @@ Multiple lookups are allowed, of course, and are translated as "AND"s::
...retrieves all polls published in January 2005 that have a question starting with "Would." ...retrieves all polls published in January 2005 that have a question starting with "Would."
For convenience, there's a ``pk`` lookup type, which translates into
``(primary_key)__exact``. In the polls example, these two statements are
equivalent::
polls.get_object(id__exact=3)
polls.get_object(pk=3)
``pk`` lookups also work across joins. In the polls example, these two
statements are equivalent::
choices.get_list(poll__id__exact=3)
choices.get_list(poll__pk=3)
Ordering Ordering
======== ========

View File

@ -93,6 +93,12 @@ is created on the fly: No code generation necessary::
... ...
django.models.news.ReporterDoesNotExist: Reporter does not exist for {'id__exact': 2} django.models.news.ReporterDoesNotExist: Reporter does not exist for {'id__exact': 2}
# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to reporters.get_object(id__exact=1).
>>> reporters.get_object(pk=1)
John Smith
# Create an article. # Create an article.
>>> from datetime import datetime >>> from datetime import datetime
>>> a = articles.Article(id=None, pub_date=datetime.now(), headline='Django is cool', article='Yeah.', reporter_id=1) >>> a = articles.Article(id=None, pub_date=datetime.now(), headline='Django is cool', article='Yeah.', reporter_id=1)
@ -200,7 +206,7 @@ article_detail from above::
def article_detail(request, year, month, article_id): def article_detail(request, year, month, article_id):
# Use the Django API to find an object matching the URL criteria. # Use the Django API to find an object matching the URL criteria.
try: try:
a = articles.get_object(pub_date__year=year, pub_date__month=month, id__exact=article_id) a = articles.get_object(pub_date__year=year, pub_date__month=month, pk=article_id)
except articles.ArticleDoesNotExist: except articles.ArticleDoesNotExist:
raise Http404 raise Http404
t = template_loader.get_template('news/article_detail') t = template_loader.get_template('news/article_detail')

View File

@ -49,10 +49,10 @@ settings. Let's look at what ``startproject`` created::
First, edit ``myproject/settings/main.py``. It's a normal Python module with First, edit ``myproject/settings/main.py``. It's a normal Python module with
module-level variables representing Django settings. Edit the file and change module-level variables representing Django settings. Edit the file and change
these settings to match your database's connection parameters: these settings to match your database's connection parameters:
* ``DATABASE_ENGINE`` -- Either 'postgresql', 'mysql' or 'sqlite3'. * ``DATABASE_ENGINE`` -- Either 'postgresql', 'mysql' or 'sqlite3'.
More coming soon. More coming soon.
* ``DATABASE_NAME`` -- The name of your database, or the full path to * ``DATABASE_NAME`` -- The name of your database, or the full path to
the database file if using sqlite. the database file if using sqlite.
* ``DATABASE_USER`` -- Your database username (not used for sqlite). * ``DATABASE_USER`` -- Your database username (not used for sqlite).
* ``DATABASE_PASSWORD`` -- Your database password (not used for sqlite). * ``DATABASE_PASSWORD`` -- Your database password (not used for sqlite).
@ -134,7 +134,7 @@ The first step in writing a database Web app in Django is to define your models
-- essentially, your database layout, with additional metadata. -- essentially, your database layout, with additional metadata.
.. admonition:: Philosophy .. admonition:: Philosophy
A model is the single, definitive source of data about your A model is the single, definitive source of data about your
data. It contains the essential fields and behaviors of the data you're data. It contains the essential fields and behaviors of the data you're
storing. Django follows the `DRY Principle`_. The goal is to define your storing. Django follows the `DRY Principle`_. The goal is to define your
@ -243,11 +243,11 @@ Note the following:
* Table names are automatically generated by combining the name of the app * Table names are automatically generated by combining the name of the app
(polls) with a plural version of the object name (polls and choices). (You (polls) with a plural version of the object name (polls and choices). (You
can override this behavior.) can override this behavior.)
* Primary keys (IDs) are added automatically. (You can override this, too.) * Primary keys (IDs) are added automatically. (You can override this, too.)
* The foreign key relationship is made explicit by a ``REFERENCES`` statement. * The foreign key relationship is made explicit by a ``REFERENCES`` statement.
* It's tailored to the database you're using, so database-specific field types * It's tailored to the database you're using, so database-specific field types
such as ``auto_increment`` (MySQL), ``serial`` (PostgreSQL), or ``integer such as ``auto_increment`` (MySQL), ``serial`` (PostgreSQL), or ``integer
primary key`` (SQLite) are handled for you automatically. The author of primary key`` (SQLite) are handled for you automatically. The author of
@ -256,16 +256,16 @@ Note the following:
If you're interested, also run the following commands: If you're interested, also run the following commands:
* ``django-admin.py sqlinitialdata polls`` -- Outputs the initial-data * ``django-admin.py sqlinitialdata polls`` -- Outputs the initial-data
inserts required for Django's admin framework. inserts required for Django's admin framework.
* ``django-admin.py sqlclear polls`` -- Outputs the necessary ``DROP * ``django-admin.py sqlclear polls`` -- Outputs the necessary ``DROP
TABLE`` statements for this app, according to which tables already exist TABLE`` statements for this app, according to which tables already exist
in your database (if any). in your database (if any).
* ``django-admin.py sqlindexes polls`` -- Outputs the ``CREATE INDEX`` * ``django-admin.py sqlindexes polls`` -- Outputs the ``CREATE INDEX``
statements for this app. statements for this app.
* ``django-admin.py sqlall polls`` -- A combination of 'sql' and * ``django-admin.py sqlall polls`` -- A combination of 'sql' and
'sqlinitialdata'. 'sqlinitialdata'.
@ -372,14 +372,20 @@ Let's jump back into the Python interactive shell::
>>> polls.get_list(question__startswith='What') >>> polls.get_list(question__startswith='What')
[What's up] [What's up]
# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to polls.get_object(id__exact=1).
>>> polls.get_object(pk=1)
What's up
# Make sure our custom method worked. # Make sure our custom method worked.
>>> p = polls.get_object(id__exact=1) >>> p = polls.get_object(pk=1)
>>> p.was_published_today() >>> p.was_published_today()
False False
# Give the Poll a couple of Choices. Each one of these method calls does an # Give the Poll a couple of Choices. Each one of these method calls does an
# INSERT statement behind the scenes and returns the new Choice object. # INSERT statement behind the scenes and returns the new Choice object.
>>> p = polls.get_object(id__exact=1) >>> p = polls.get_object(pk=1)
>>> p.add_choice(choice='Not much', votes=0) >>> p.add_choice(choice='Not much', votes=0)
Not much Not much
>>> p.add_choice(choice='The sky', votes=0) >>> p.add_choice(choice='The sky', votes=0)

View File

@ -242,7 +242,7 @@ for a given poll. Here's the view::
from django.core.exceptions import Http404 from django.core.exceptions import Http404
def detail(request, poll_id): def detail(request, poll_id):
try: try:
p = polls.get_object(id__exact=poll_id) p = polls.get_object(pk=poll_id)
except polls.PollDoesNotExist: except polls.PollDoesNotExist:
raise Http404 raise Http404
t = template_loader.get_template('polls/detail') t = template_loader.get_template('polls/detail')