diff --git a/django/contrib/comments/models/comments.py b/django/contrib/comments/models/comments.py index 83b60f88c3..b9ee199156 100644 --- a/django/contrib/comments/models/comments.py +++ b/django/contrib/comments/models/comments.py @@ -78,7 +78,7 @@ class Comment(meta.Model): """ from django.core.exceptions import ObjectDoesNotExist 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: return None @@ -193,7 +193,7 @@ class FreeComment(meta.Model): """ from django.core.exceptions import ObjectDoesNotExist 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: return None diff --git a/django/contrib/comments/templatetags/comments.py b/django/contrib/comments/templatetags/comments.py index 99bf50fd12..bb7150fc17 100644 --- a/django/contrib/comments/templatetags/comments.py +++ b/django/contrib/comments/templatetags/comments.py @@ -80,7 +80,7 @@ class CommentFormNode(template.Node): # We only have to do this validation if obj_id_lookup_var is provided, # because do_comment_form() validates hard-coded object IDs. 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: context['display_form'] = False else: @@ -203,7 +203,7 @@ class DoCommentForm: if tokens[3].isdigit(): obj_id = tokens[3] 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: 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: @@ -283,7 +283,7 @@ class DoCommentCount: if tokens[3].isdigit(): obj_id = tokens[3] 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: 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: @@ -338,7 +338,7 @@ class DoGetCommentList: if tokens[3].isdigit(): obj_id = tokens[3] 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: 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: diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py index 1a28676a88..c14df2cfdd 100644 --- a/django/contrib/comments/views/comments.py +++ b/django/contrib/comments/views/comments.py @@ -197,7 +197,7 @@ def post_comment(request): rating_range, rating_choices = [], [] content_type_id, object_id = target.split(':') # target is something like '52:5157' 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: 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' @@ -284,9 +284,9 @@ def post_free_comment(request): if comments.get_security_hash(options, '', '', target) != security_hash: 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 = contenttypes.get_object(id__exact=content_type_id) + content_type = contenttypes.get_object(pk=content_type_id) 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: raise Http404, "The comment form had an invalid 'target' parameter -- the object ID was invalid" option_list = options.split(',') @@ -336,8 +336,8 @@ def comment_was_posted(request): if request.GET.has_key('c'): content_type_id, object_id = request.GET['c'].split(':') try: - content_type = contenttypes.get_object(id__exact=content_type_id) - obj = content_type.get_object_for_this_type(id__exact=object_id) + content_type = contenttypes.get_object(pk=content_type_id) + obj = content_type.get_object_for_this_type(pk=object_id) except ObjectDoesNotExist: pass t = template_loader.get_template('comments/posted') diff --git a/django/contrib/comments/views/karma.py b/django/contrib/comments/views/karma.py index afc115c0ef..7422047fad 100644 --- a/django/contrib/comments/views/karma.py +++ b/django/contrib/comments/views/karma.py @@ -19,14 +19,14 @@ def vote(request, comment_id, vote): if request.user.is_anonymous(): raise Http404, "Anonymous users cannot vote" try: - comment = comments.get_object(id__exact=comment_id) + comment = comments.get_object(pk=comment_id) except comments.CommentDoesNotExist: raise Http404, "Invalid comment ID" if comment.user_id == request.user.id: raise Http404, "No voting for yourself" karma.vote(request.user.id, comment_id, rating) # 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') c = Context(request, { 'comment': comment diff --git a/django/contrib/comments/views/userflags.py b/django/contrib/comments/views/userflags.py index f0d0715cdd..59664f3cab 100644 --- a/django/contrib/comments/views/userflags.py +++ b/django/contrib/comments/views/userflags.py @@ -16,7 +16,7 @@ def flag(request, comment_id): the flagged `comments.comments` object """ 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: raise Http404 if request.POST: @@ -31,7 +31,7 @@ flag = login_required(flag) def flag_done(request, comment_id): 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: raise Http404 t = template_loader.get_template('comments/flag_done') @@ -50,7 +50,7 @@ def delete(request, comment_id): the flagged `comments.comments` object """ 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: raise Http404 if not comments.user_is_moderator(request.user): @@ -72,7 +72,7 @@ delete = login_required(delete) def delete_done(request, comment_id): 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: raise Http404 t = template_loader.get_template('comments/delete_done') diff --git a/django/core/meta.py b/django/core/meta.py index f16a5ad20b..51cdee2b63 100644 --- a/django/core/meta.py +++ b/django/core/meta.py @@ -1146,6 +1146,9 @@ def _parse_lookup(kwarg_items, opts, table_count=0): params.extend(params2) continue 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: _throw_bad_kwarg_error(kwarg) lookup_type = lookup_list.pop() diff --git a/django/models/auth.py b/django/models/auth.py index d0d2bf5002..63e879fdea 100644 --- a/django/models/auth.py +++ b/django/models/auth.py @@ -222,7 +222,7 @@ class Session(meta.Model): "Sets the necessary cookie in the given HttpResponse object, also updates last login time for user." from django.models.auth import users 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.save() session = create_session(user_id) @@ -274,7 +274,7 @@ class LogEntry(meta.Model): def get_edited_object(self): "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): """ diff --git a/django/models/core.py b/django/models/core.py index bf74610db2..18c9e90072 100644 --- a/django/models/core.py +++ b/django/models/core.py @@ -14,7 +14,7 @@ class Site(meta.Model): def _module_get_current(): "Returns the current site, according to the SITE_ID constant." from django.conf.settings import SITE_ID - return get_object(id__exact=SITE_ID) + return get_object(pk=SITE_ID) class Package(meta.Model): db_table = 'packages' diff --git a/django/views/admin/doc.py b/django/views/admin/doc.py index 3b593e5944..8395ebc740 100644 --- a/django/views/admin/doc.py +++ b/django/views/admin/doc.py @@ -1,6 +1,3 @@ -import os -import re -import inspect from django.core import meta from django import templatetags from django.conf import settings @@ -14,6 +11,7 @@ try: from django.parts.admin import doc except ImportError: doc = None +import inspect, os, re # Exclude methods starting with these strings from documentation MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_') @@ -128,7 +126,7 @@ def view_index(request): 'module' : func.__module__, 'title' : title, '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), }) t = template_loader.get_template('doc/view_index') diff --git a/django/views/defaults.py b/django/views/defaults.py index 7d362f18b2..4da2643c3d 100644 --- a/django/views/defaults.py +++ b/django/views/defaults.py @@ -7,8 +7,8 @@ from django.utils import httpwrappers def shortcut(request, content_type_id, object_id): from django.models.core import contenttypes try: - content_type = contenttypes.get_object(id__exact=content_type_id) - obj = content_type.get_object_for_this_type(id__exact=object_id) + content_type = contenttypes.get_object(pk=content_type_id) + obj = content_type.get_object_for_this_type(pk=object_id) except ObjectDoesNotExist: raise Http404, "Content type %s object %s doesn't exist" % (content_type_id, object_id) if not hasattr(obj, 'get_absolute_url'): diff --git a/docs/db-api.txt b/docs/db-api.txt index 401d9d3260..cb5ec4783f 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -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." +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 ======== diff --git a/docs/overview.txt b/docs/overview.txt index 86f089778d..7fba5e1767 100644 --- a/docs/overview.txt +++ b/docs/overview.txt @@ -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} + # 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. >>> from datetime import datetime >>> 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): # Use the Django API to find an object matching the URL criteria. 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: raise Http404 t = template_loader.get_template('news/article_detail') diff --git a/docs/tutorial01.txt b/docs/tutorial01.txt index 4e377bd97d..e92e9c2ccd 100644 --- a/docs/tutorial01.txt +++ b/docs/tutorial01.txt @@ -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 module-level variables representing Django settings. Edit the file and change 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. - * ``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. * ``DATABASE_USER`` -- Your database username (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. .. admonition:: Philosophy - + A model is the single, definitive source of data about your 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 @@ -243,11 +243,11 @@ Note the following: * 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 can override this behavior.) - + * Primary keys (IDs) are added automatically. (You can override this, too.) - + * 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 such as ``auto_increment`` (MySQL), ``serial`` (PostgreSQL), or ``integer 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: - * ``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. - - * ``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 in your database (if any). - + * ``django-admin.py sqlindexes polls`` -- Outputs the ``CREATE INDEX`` statements for this app. - + * ``django-admin.py sqlall polls`` -- A combination of 'sql' and 'sqlinitialdata'. @@ -372,14 +372,20 @@ Let's jump back into the Python interactive shell:: >>> polls.get_list(question__startswith='What') [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. - >>> p = polls.get_object(id__exact=1) + >>> p = polls.get_object(pk=1) >>> p.was_published_today() False # 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. - >>> p = polls.get_object(id__exact=1) + >>> p = polls.get_object(pk=1) >>> p.add_choice(choice='Not much', votes=0) Not much >>> p.add_choice(choice='The sky', votes=0) diff --git a/docs/tutorial03.txt b/docs/tutorial03.txt index 1c547a670f..3f2f97b7b1 100644 --- a/docs/tutorial03.txt +++ b/docs/tutorial03.txt @@ -242,7 +242,7 @@ for a given poll. Here's the view:: from django.core.exceptions import Http404 def detail(request, poll_id): try: - p = polls.get_object(id__exact=poll_id) + p = polls.get_object(pk=poll_id) except polls.PollDoesNotExist: raise Http404 t = template_loader.get_template('polls/detail')