Fixed #122 -- BIG, BACKWARDS-INCOMPATIBLE CHANGE. Changed model syntax to use fieldname=FieldClass() syntax. See ModelSyntaxChangeInstructions for important information on how to change your models

git-svn-id: http://code.djangoproject.com/svn/django/trunk@549 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2005-08-25 22:51:30 +00:00
parent aec0a73d73
commit 25264c8604
36 changed files with 956 additions and 720 deletions

View File

@ -2,59 +2,56 @@ from django.core import meta
from django.models import auth, core from django.models import auth, core
class Comment(meta.Model): class Comment(meta.Model):
db_table = 'comments' user = meta.ForeignKey(auth.User, raw_id_admin=True)
fields = ( content_type = meta.ForeignKey(core.ContentType)
meta.ForeignKey(auth.User, raw_id_admin=True), object_id = meta.IntegerField('object ID')
meta.ForeignKey(core.ContentType, name='content_type_id', rel_name='content_type'), headline = meta.CharField(maxlength=255, blank=True)
meta.IntegerField('object_id', 'object ID'), comment = meta.TextField(maxlength=3000)
meta.CharField('headline', 'headline', maxlength=255, blank=True), rating1 = meta.PositiveSmallIntegerField('rating #1', blank=True, null=True)
meta.TextField('comment', 'comment', maxlength=3000), rating2 = meta.PositiveSmallIntegerField('rating #2', blank=True, null=True)
meta.PositiveSmallIntegerField('rating1', 'rating #1', blank=True, null=True), rating3 = meta.PositiveSmallIntegerField('rating #3', blank=True, null=True)
meta.PositiveSmallIntegerField('rating2', 'rating #2', blank=True, null=True), rating4 = meta.PositiveSmallIntegerField('rating #4', blank=True, null=True)
meta.PositiveSmallIntegerField('rating3', 'rating #3', blank=True, null=True), rating5 = meta.PositiveSmallIntegerField('rating #5', blank=True, null=True)
meta.PositiveSmallIntegerField('rating4', 'rating #4', blank=True, null=True), rating6 = meta.PositiveSmallIntegerField('rating #6', blank=True, null=True)
meta.PositiveSmallIntegerField('rating5', 'rating #5', blank=True, null=True), rating7 = meta.PositiveSmallIntegerField('rating #7', blank=True, null=True)
meta.PositiveSmallIntegerField('rating6', 'rating #6', blank=True, null=True), rating8 = meta.PositiveSmallIntegerField('rating #8', blank=True, null=True)
meta.PositiveSmallIntegerField('rating7', 'rating #7', blank=True, null=True), # This field designates whether to use this row's ratings in aggregate
meta.PositiveSmallIntegerField('rating8', 'rating #8', blank=True, null=True), # functions (summaries). We need this because people are allowed to post
# This field designates whether to use this row's ratings in # multiple reviews on the same thing, but the system will only use the
# aggregate functions (summaries). We need this because people are # latest one (with valid_rating=True) in tallying the reviews.
# allowed to post multiple review on the same thing, but the system valid_rating = meta.BooleanField('is valid rating')
# will only use the latest one (with valid_rating=True) in tallying submit_date = meta.DateTimeField('date/time submitted', auto_now_add=True)
# the reviews. is_public = meta.BooleanField()
meta.BooleanField('valid_rating', 'is valid rating'), ip_address = meta.IPAddressField('IP address', blank=True, null=True)
meta.DateTimeField('submit_date', 'date/time submitted', auto_now_add=True), is_removed = meta.BooleanField(help_text='Check this box if the comment is inappropriate. A "This comment has been removed" message will be displayed instead.')
meta.BooleanField('is_public', 'is public'), site = meta.ForeignKey(core.Site)
meta.IPAddressField('ip_address', 'IP address', blank=True, null=True), class META:
meta.BooleanField('is_removed', 'is removed', db_table = 'comments'
help_text='Check this box if the comment is inappropriate. A "This comment has been removed" message will be displayed instead.'), module_constants = {
meta.ForeignKey(core.Site), # min. and max. allowed dimensions for photo resizing (in pixels)
) 'MIN_PHOTO_DIMENSION': 5,
module_constants = { 'MAX_PHOTO_DIMENSION': 1000,
# min. and max. allowed dimensions for photo resizing (in pixels)
'MIN_PHOTO_DIMENSION': 5,
'MAX_PHOTO_DIMENSION': 1000,
# option codes for comment-form hidden fields # option codes for comment-form hidden fields
'PHOTOS_REQUIRED': 'pr', 'PHOTOS_REQUIRED': 'pr',
'PHOTOS_OPTIONAL': 'pa', 'PHOTOS_OPTIONAL': 'pa',
'RATINGS_REQUIRED': 'rr', 'RATINGS_REQUIRED': 'rr',
'RATINGS_OPTIONAL': 'ra', 'RATINGS_OPTIONAL': 'ra',
'IS_PUBLIC': 'ip', 'IS_PUBLIC': 'ip',
} }
ordering = ('-submit_date',) ordering = ('-submit_date',)
admin = meta.Admin( admin = meta.Admin(
fields = ( fields = (
(None, {'fields': ('content_type_id', 'object_id', 'site_id')}), (None, {'fields': ('content_type', 'object_id', 'site')}),
('Content', {'fields': ('user_id', 'headline', 'comment')}), ('Content', {'fields': ('user', 'headline', 'comment')}),
('Ratings', {'fields': ('rating1', 'rating2', 'rating3', 'rating4', 'rating5', 'rating6', 'rating7', 'rating8', 'valid_rating')}), ('Ratings', {'fields': ('rating1', 'rating2', 'rating3', 'rating4', 'rating5', 'rating6', 'rating7', 'rating8', 'valid_rating')}),
('Meta', {'fields': ('is_public', 'is_removed', 'ip_address')}), ('Meta', {'fields': ('is_public', 'is_removed', 'ip_address')}),
), ),
list_display = ('user_id', 'submit_date', 'content_type_id', 'get_content_object'), list_display = ('user', 'submit_date', 'content_type', 'get_content_object'),
list_filter = ('submit_date',), list_filter = ('submit_date',),
date_hierarchy = 'submit_date', date_hierarchy = 'submit_date',
search_fields = ('comment', 'user__username'), search_fields = ('comment', 'user__username'),
) )
def __repr__(self): def __repr__(self):
return "%s: %s..." % (self.get_user().username, self.comment[:100]) return "%s: %s..." % (self.get_user().username, self.comment[:100])
@ -156,32 +153,31 @@ class Comment(meta.Model):
return False return False
class FreeComment(meta.Model): class FreeComment(meta.Model):
"A FreeComment is a comment by a non-registered user" # A FreeComment is a comment by a non-registered user.
db_table = 'comments_free' content_type = meta.ForeignKey(core.ContentType)
fields = ( object_id = meta.IntegerField('object ID')
meta.ForeignKey(core.ContentType, name='content_type_id', rel_name='content_type'), comment = meta.TextField(maxlength=3000)
meta.IntegerField('object_id', 'object ID'), person_name = meta.CharField("person's name", maxlength=50)
meta.TextField('comment', 'comment', maxlength=3000), submit_date = meta.DateTimeField('date/time submitted', auto_now_add=True)
meta.CharField('person_name', "person's name", maxlength=50), is_public = meta.BooleanField()
meta.DateTimeField('submit_date', 'date/time submitted', auto_now_add=True), ip_address = meta.IPAddressField()
meta.BooleanField('is_public', 'is public'), # TODO: Change this to is_removed, like Comment
meta.IPAddressField('ip_address', 'IP address'), approved = meta.BooleanField('approved by staff')
# TODO: Change this to is_removed, like Comment site = meta.ForeignKey(core.Site)
meta.BooleanField('approved', 'approved by staff'), class META:
meta.ForeignKey(core.Site), db_table = 'comments_free'
) ordering = ('-submit_date',)
ordering = ('-submit_date',) admin = meta.Admin(
admin = meta.Admin( fields = (
fields = ( (None, {'fields': ('content_type', 'object_id', 'site')}),
(None, {'fields': ('content_type_id', 'object_id', 'site_id')}), ('Content', {'fields': ('person_name', 'comment')}),
('Content', {'fields': ('person_name', 'comment')}), ('Meta', {'fields': ('submit_date', 'is_public', 'ip_address', 'approved')}),
('Meta', {'fields': ('submit_date', 'is_public', 'ip_address', 'approved')}), ),
), list_display = ('person_name', 'submit_date', 'content_type', 'get_content_object'),
list_display = ('person_name', 'submit_date', 'content_type_id', 'get_content_object'), list_filter = ('submit_date',),
list_filter = ('submit_date',), date_hierarchy = 'submit_date',
date_hierarchy = 'submit_date', search_fields = ('comment', 'person_name'),
search_fields = ('comment', 'person_name'), )
)
def __repr__(self): def __repr__(self):
return "%s: %s..." % (self.person_name, self.comment[:100]) return "%s: %s..." % (self.person_name, self.comment[:100])
@ -203,26 +199,25 @@ class FreeComment(meta.Model):
get_content_object.short_description = 'Content object' get_content_object.short_description = 'Content object'
class KarmaScore(meta.Model): class KarmaScore(meta.Model):
module_name = 'karma' user = meta.ForeignKey(auth.User)
fields = ( comment = meta.ForeignKey(Comment)
meta.ForeignKey(auth.User), score = meta.SmallIntegerField(db_index=True)
meta.ForeignKey(Comment), scored_date = meta.DateTimeField(auto_now=True)
meta.SmallIntegerField('score', 'score', db_index=True), class META:
meta.DateTimeField('scored_date', 'date scored', auto_now=True), module_name = 'karma'
) unique_together = (('user', 'comment'),)
unique_together = (('user_id', 'comment_id'),) module_constants = {
module_constants = { # what users get if they don't have any karma
# what users get if they don't have any karma 'DEFAULT_KARMA': 5,
'DEFAULT_KARMA': 5, 'KARMA_NEEDED_BEFORE_DISPLAYED': 3,
'KARMA_NEEDED_BEFORE_DISPLAYED': 3, }
}
def __repr__(self): def __repr__(self):
return "%d rating by %s" % (self.score, self.get_user()) return "%d rating by %s" % (self.score, self.get_user())
def _module_vote(user_id, comment_id, score): def _module_vote(user_id, comment_id, score):
try: try:
karma = get_object(comment_id__exact=comment_id, user_id__exact=user_id) karma = get_object(comment__id__exact=comment_id, user__id__exact=user_id)
except KarmaScoreDoesNotExist: except KarmaScoreDoesNotExist:
karma = KarmaScore(None, user_id, comment_id, score, datetime.datetime.now()) karma = KarmaScore(None, user_id, comment_id, score, datetime.datetime.now())
karma.save() karma.save()
@ -241,13 +236,12 @@ class KarmaScore(meta.Model):
return int(round((4.5 * score) + 5.5)) return int(round((4.5 * score) + 5.5))
class UserFlag(meta.Model): class UserFlag(meta.Model):
db_table = 'comments_user_flags' user = meta.ForeignKey(auth.User)
fields = ( comment = meta.ForeignKey(Comment)
meta.ForeignKey(auth.User), flag_date = meta.DateTimeField(auto_now_add=True)
meta.ForeignKey(Comment), class META:
meta.DateTimeField('flag_date', 'date flagged', auto_now_add=True), db_table = 'comments_user_flags'
) unique_together = (('user', 'comment'),)
unique_together = (('user_id', 'comment_id'),)
def __repr__(self): def __repr__(self):
return "Flag by %r" % self.get_user() return "Flag by %r" % self.get_user()
@ -261,7 +255,7 @@ class UserFlag(meta.Model):
if int(comment.user_id) == int(user.id): if int(comment.user_id) == int(user.id):
return # A user can't flag his own comment. Fail silently. return # A user can't flag his own comment. Fail silently.
try: try:
f = get_object(user_id__exact=user.id, comment_id__exact=comment.id) f = get_object(user__id__exact=user.id, comment__id__exact=comment.id)
except UserFlagDoesNotExist: except UserFlagDoesNotExist:
from django.core.mail import mail_managers from django.core.mail import mail_managers
f = UserFlag(None, user.id, comment.id, None) f = UserFlag(None, user.id, comment.id, None)
@ -270,13 +264,12 @@ class UserFlag(meta.Model):
f.save() f.save()
class ModeratorDeletion(meta.Model): class ModeratorDeletion(meta.Model):
db_table = 'comments_moderator_deletions' user = meta.ForeignKey(auth.User, verbose_name='moderator')
fields = ( comment = meta.ForeignKey(Comment)
meta.ForeignKey(auth.User, verbose_name='moderator'), deletion_date = meta.DateTimeField(auto_now_add=True)
meta.ForeignKey(Comment), class META:
meta.DateTimeField('deletion_date', 'date deleted', auto_now_add=True), db_table = 'comments_moderator_deletions'
) unique_together = (('user', 'comment'),)
unique_together = (('user_id', 'comment_id'),)
def __repr__(self): def __repr__(self):
return "Moderator deletion by %r" % self.get_user() return "Moderator deletion by %r" % self.get_user()

View File

@ -123,7 +123,7 @@ class CommentCountNode(template.Node):
self.obj_id = template.resolve_variable(self.context_var_name, context) self.obj_id = template.resolve_variable(self.context_var_name, context)
comment_count = get_count_function(object_id__exact=self.obj_id, comment_count = get_count_function(object_id__exact=self.obj_id,
content_type__package__label__exact=self.package, content_type__package__label__exact=self.package,
content_type__python_module_name__exact=self.module, site_id__exact=SITE_ID) content_type__python_module_name__exact=self.module, site__id__exact=SITE_ID)
context[self.var_name] = comment_count context[self.var_name] = comment_count
return '' return ''
@ -146,7 +146,7 @@ class CommentListNode(template.Node):
'object_id__exact': self.obj_id, 'object_id__exact': self.obj_id,
'content_type__package__label__exact': self.package, 'content_type__package__label__exact': self.package,
'content_type__python_module_name__exact': self.module, 'content_type__python_module_name__exact': self.module,
'site_id__exact': SITE_ID, 'site__id__exact': SITE_ID,
'select_related': True, 'select_related': True,
'order_by': (self.ordering + 'submit_date',), 'order_by': (self.ordering + 'submit_date',),
} }

View File

@ -86,8 +86,8 @@ class PublicCommentManipulator(AuthenticationForm):
def save(self, new_data): def save(self, new_data):
today = datetime.date.today() today = datetime.date.today()
c = self.get_comment(new_data) c = self.get_comment(new_data)
for old in comments.get_list(content_type_id__exact=new_data["content_type_id"], for old in comments.get_list(content_type__id__exact=new_data["content_type_id"],
object_id__exact=new_data["object_id"], user_id__exact=self.get_user_id()): object_id__exact=new_data["object_id"], user__id__exact=self.get_user_id()):
# Check that this comment isn't duplicate. (Sometimes people post # Check that this comment isn't duplicate. (Sometimes people post
# comments twice by mistake.) If it is, fail silently by pretending # comments twice by mistake.) If it is, fail silently by pretending
# the comment was posted successfully. # the comment was posted successfully.
@ -141,7 +141,7 @@ class PublicFreeCommentManipulator(formfields.Manipulator):
# Check that this comment isn't duplicate. (Sometimes people post # Check that this comment isn't duplicate. (Sometimes people post
# comments twice by mistake.) If it is, fail silently by pretending # comments twice by mistake.) If it is, fail silently by pretending
# the comment was posted successfully. # the comment was posted successfully.
for old_comment in freecomments.get_list(content_type_id__exact=new_data["content_type_id"], for old_comment in freecomments.get_list(content_type__id__exact=new_data["content_type_id"],
object_id__exact=new_data["object_id"], person_name__exact=new_data["person_name"], object_id__exact=new_data["object_id"], person_name__exact=new_data["person_name"],
submit_date__year=today.year, submit_date__month=today.month, submit_date__year=today.year, submit_date__month=today.month,
submit_date__day=today.day): submit_date__day=today.day):

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(pk=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(pk=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(pk=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(pk=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

@ -67,7 +67,7 @@ def get_sql_create(mod):
data_type = f.__class__.__name__ data_type = f.__class__.__name__
col_type = db.DATA_TYPES[data_type] col_type = db.DATA_TYPES[data_type]
if col_type is not None: if col_type is not None:
field_output = [f.name, col_type % rel_field.__dict__] field_output = [f.column, col_type % rel_field.__dict__]
field_output.append('%sNULL' % (not f.null and 'NOT ' or '')) field_output.append('%sNULL' % (not f.null and 'NOT ' or ''))
if f.unique: if f.unique:
field_output.append('UNIQUE') field_output.append('UNIQUE')
@ -75,12 +75,12 @@ def get_sql_create(mod):
field_output.append('PRIMARY KEY') field_output.append('PRIMARY KEY')
if f.rel: if f.rel:
field_output.append('REFERENCES %s (%s)' % \ field_output.append('REFERENCES %s (%s)' % \
(f.rel.to.db_table, f.rel.to.get_field(f.rel.field_name).name)) (f.rel.to.db_table, f.rel.to.get_field(f.rel.field_name).column))
table_output.append(' '.join(field_output)) table_output.append(' '.join(field_output))
if opts.order_with_respect_to: if opts.order_with_respect_to:
table_output.append('_order %s NULL' % db.DATA_TYPES['IntegerField']) table_output.append('_order %s NULL' % db.DATA_TYPES['IntegerField'])
for field_constraints in opts.unique_together: for field_constraints in opts.unique_together:
table_output.append('UNIQUE (%s)' % ", ".join(field_constraints)) table_output.append('UNIQUE (%s)' % ", ".join([opts.get_field(f).column for f in field_constraints]))
full_statement = ['CREATE TABLE %s (' % opts.db_table] full_statement = ['CREATE TABLE %s (' % opts.db_table]
for i, line in enumerate(table_output): # Combine and add commas. for i, line in enumerate(table_output): # Combine and add commas.
@ -94,9 +94,9 @@ def get_sql_create(mod):
table_output = ['CREATE TABLE %s (' % f.get_m2m_db_table(opts)] table_output = ['CREATE TABLE %s (' % f.get_m2m_db_table(opts)]
table_output.append(' id %s NOT NULL PRIMARY KEY,' % db.DATA_TYPES['AutoField']) table_output.append(' id %s NOT NULL PRIMARY KEY,' % db.DATA_TYPES['AutoField'])
table_output.append(' %s_id %s NOT NULL REFERENCES %s (%s),' % \ table_output.append(' %s_id %s NOT NULL REFERENCES %s (%s),' % \
(opts.object_name.lower(), db.DATA_TYPES['IntegerField'], opts.db_table, opts.pk.name)) (opts.object_name.lower(), db.DATA_TYPES['IntegerField'], opts.db_table, opts.pk.column))
table_output.append(' %s_id %s NOT NULL REFERENCES %s (%s),' % \ table_output.append(' %s_id %s NOT NULL REFERENCES %s (%s),' % \
(f.rel.to.object_name.lower(), db.DATA_TYPES['IntegerField'], f.rel.to.db_table, f.rel.to.pk.name)) (f.rel.to.object_name.lower(), db.DATA_TYPES['IntegerField'], f.rel.to.db_table, f.rel.to.pk.column))
table_output.append(' UNIQUE (%s_id, %s_id)' % (opts.object_name.lower(), f.rel.to.object_name.lower())) table_output.append(' UNIQUE (%s_id, %s_id)' % (opts.object_name.lower(), f.rel.to.object_name.lower()))
table_output.append(');') table_output.append(');')
final_output.append('\n'.join(table_output)) final_output.append('\n'.join(table_output))
@ -186,7 +186,7 @@ def get_sql_sequence_reset(mod):
for klass in mod._MODELS: for klass in mod._MODELS:
for f in klass._meta.fields: for f in klass._meta.fields:
if isinstance(f, meta.AutoField): if isinstance(f, meta.AutoField):
output.append("SELECT setval('%s_%s_seq', (SELECT max(%s) FROM %s));" % (klass._meta.db_table, f.name, f.name, klass._meta.db_table)) output.append("SELECT setval('%s_%s_seq', (SELECT max(%s) FROM %s));" % (klass._meta.db_table, f.column, f.column, klass._meta.db_table))
return output return output
get_sql_sequence_reset.help_doc = "Prints the SQL statements for resetting PostgreSQL sequences for the given app(s)." get_sql_sequence_reset.help_doc = "Prints the SQL statements for resetting PostgreSQL sequences for the given app(s)."
get_sql_sequence_reset.args = APP_ARGS get_sql_sequence_reset.args = APP_ARGS
@ -199,7 +199,7 @@ def get_sql_indexes(mod):
if f.db_index: if f.db_index:
unique = f.unique and "UNIQUE " or "" unique = f.unique and "UNIQUE " or ""
output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \ output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \
(unique, klass._meta.db_table, f.name, klass._meta.db_table, f.name)) (unique, klass._meta.db_table, f.column, klass._meta.db_table, f.column))
return output return output
get_sql_indexes.help_doc = "Prints the CREATE INDEX SQL statements for the given app(s)." get_sql_indexes.help_doc = "Prints the CREATE INDEX SQL statements for the given app(s)."
get_sql_indexes.args = APP_ARGS get_sql_indexes.args = APP_ARGS
@ -490,7 +490,7 @@ class ModelErrorCollection:
def add(self, opts, error): def add(self, opts, error):
self.errors.append((opts, error)) self.errors.append((opts, error))
self.outfile.write("%s.%s: %s\n" % (opts.module_name, opts.object_name, error)) self.outfile.write("%s.%s: %s\n" % (opts.app_label, opts.module_name, error))
def validate(): def validate():
"Validates all installed models." "Validates all installed models."
@ -524,6 +524,8 @@ def validate():
if field_name == '?': continue if field_name == '?': continue
if field_name.startswith('-'): if field_name.startswith('-'):
field_name = field_name[1:] field_name = field_name[1:]
if opts.order_with_respect_to and field_name == '_order':
continue
try: try:
opts.get_field(field_name, many_to_many=False) opts.get_field(field_name, many_to_many=False)
except meta.FieldDoesNotExist: except meta.FieldDoesNotExist:

View File

@ -50,15 +50,21 @@ def handle_legacy_orderlist(order_list):
warnings.warn("%r ordering syntax is deprecated. Use %r instead." % (order_list, new_order_list), DeprecationWarning) warnings.warn("%r ordering syntax is deprecated. Use %r instead." % (order_list, new_order_list), DeprecationWarning)
return new_order_list return new_order_list
def orderlist2sql(order_list, prefix=''): def orderfield2column(f, opts):
try:
return opts.get_field(f, False).column
except FieldDoesNotExist:
return f
def orderlist2sql(order_list, opts, prefix=''):
output = [] output = []
for f in handle_legacy_orderlist(order_list): for f in handle_legacy_orderlist(order_list):
if f.startswith('-'): if f.startswith('-'):
output.append('%s%s DESC' % (prefix, f[1:])) output.append('%s%s DESC' % (prefix, orderfield2column(f[1:], opts)))
elif f == '?': elif f == '?':
output.append('RANDOM()') output.append('RANDOM()')
else: else:
output.append('%s%s ASC' % (prefix, f)) output.append('%s%s ASC' % (prefix, orderfield2column(f, opts)))
return ', '.join(output) return ', '.join(output)
def get_module(app_label, module_name): def get_module(app_label, module_name):
@ -206,7 +212,7 @@ class Options:
# If a primary_key field hasn't been specified, add an # If a primary_key field hasn't been specified, add an
# auto-incrementing primary-key ID field automatically. # auto-incrementing primary-key ID field automatically.
if self.pk is None: if self.pk is None:
self.fields.insert(0, AutoField('id', 'ID', primary_key=True)) self.fields.insert(0, AutoField(name='id', verbose_name='ID', primary_key=True))
self.pk = self.fields[0] self.pk = self.fields[0]
# Cache whether this has an AutoField. # Cache whether this has an AutoField.
self.has_auto_field = False self.has_auto_field = False
@ -249,7 +255,7 @@ class Options:
"Returns the full 'ORDER BY' clause for this object, according to self.ordering." "Returns the full 'ORDER BY' clause for this object, according to self.ordering."
if not self.ordering: return '' if not self.ordering: return ''
pre = table_prefix and (table_prefix + '.') or '' pre = table_prefix and (table_prefix + '.') or ''
return 'ORDER BY ' + orderlist2sql(self.ordering, pre) return 'ORDER BY ' + orderlist2sql(self.ordering, self, pre)
def get_add_permission(self): def get_add_permission(self):
return 'add_%s' % self.object_name.lower() return 'add_%s' % self.object_name.lower()
@ -298,7 +304,7 @@ class Options:
# subsequently loaded object with related links will override this # subsequently loaded object with related links will override this
# relationship we're adding. # relationship we're adding.
link_field = copy.copy(core.RelatedLink._meta.get_field('object_id')) link_field = copy.copy(core.RelatedLink._meta.get_field('object_id'))
link_field.rel = ManyToOne(self.get_model_module().Klass, 'related_links', 'id', link_field.rel = ManyToOne(self.get_model_module().Klass, 'id',
num_in_admin=3, min_num_in_admin=3, edit_inline=TABULAR, num_in_admin=3, min_num_in_admin=3, edit_inline=TABULAR,
lookup_overrides={ lookup_overrides={
'content_type__package__label__exact': self.app_label, 'content_type__package__label__exact': self.app_label,
@ -386,34 +392,48 @@ class ModelBase(type):
if not bases: if not bases:
return type.__new__(cls, name, bases, attrs) return type.__new__(cls, name, bases, attrs)
# If this model is a subclass of another Model, create an Options try:
meta_attrs = attrs.pop('META').__dict__
del meta_attrs['__module__']
del meta_attrs['__doc__']
except KeyError:
meta_attrs = {}
# Gather all attributes that are Field instances.
fields = []
for obj_name, obj in attrs.items():
if isinstance(obj, Field):
obj.set_name(obj_name)
fields.append(obj)
del attrs[obj_name]
# Sort the fields in the order that they were created. The
# "creation_counter" is needed because metaclasses don't preserve the
# attribute order.
fields.sort(lambda x, y: x.creation_counter - y.creation_counter)
# If this model is a subclass of another model, create an Options
# object by first copying the base class's _meta and then updating it # object by first copying the base class's _meta and then updating it
# with the overrides from this class. # with the overrides from this class.
replaces_module = None replaces_module = None
if bases[0] != Model: if bases[0] != Model:
if not attrs.has_key('fields'): field_names = [f.name for f in fields]
attrs['fields'] = list(bases[0]._meta._orig_init_args['fields'][:]) remove_fields = meta_attrs.pop('remove_fields', [])
if attrs.has_key('ignore_fields'): for f in bases[0]._meta._orig_init_args['fields']:
ignore_fields = attrs.pop('ignore_fields') if f.name not in field_names and f.name not in remove_fields:
new_fields = [] fields.insert(0, f)
for i, f in enumerate(attrs['fields']): if meta_attrs.has_key('replaces_module'):
if f.name not in ignore_fields:
new_fields.append(f)
attrs['fields'] = new_fields
if attrs.has_key('add_fields'):
attrs['fields'].extend(attrs.pop('add_fields'))
if attrs.has_key('replaces_module'):
# Set the replaces_module variable for now. We can't actually # Set the replaces_module variable for now. We can't actually
# do anything with it yet, because the module hasn't yet been # do anything with it yet, because the module hasn't yet been
# created. # created.
replaces_module = attrs.pop('replaces_module').split('.') replaces_module = meta_attrs.pop('replaces_module').split('.')
# Pass any Options overrides to the base's Options instance, and # Pass any Options overrides to the base's Options instance, and
# simultaneously remove them from attrs. When this is done, attrs # simultaneously remove them from attrs. When this is done, attrs
# will be a dictionary of custom methods, plus __module__. # will be a dictionary of custom methods, plus __module__.
meta_overrides = {} meta_overrides = {'fields': fields}
for k, v in attrs.items(): for k, v in meta_attrs.items():
if not callable(v) and k != '__module__': if not callable(v) and k != '__module__':
meta_overrides[k] = attrs.pop(k) meta_overrides[k] = meta_attrs.pop(k)
opts = bases[0]._meta.copy(**meta_overrides) opts = bases[0]._meta.copy(**meta_overrides)
opts.object_name = name opts.object_name = name
del meta_overrides del meta_overrides
@ -422,28 +442,31 @@ class ModelBase(type):
# If the module_name wasn't given, use the class name # If the module_name wasn't given, use the class name
# in lowercase, plus a trailing "s" -- a poor-man's # in lowercase, plus a trailing "s" -- a poor-man's
# pluralization. # pluralization.
module_name = attrs.pop('module_name', name.lower() + 's'), module_name = meta_attrs.pop('module_name', name.lower() + 's'),
# If the verbose_name wasn't given, use the class name, # If the verbose_name wasn't given, use the class name,
# converted from InitialCaps to "lowercase with spaces". # converted from InitialCaps to "lowercase with spaces".
verbose_name = attrs.pop('verbose_name', verbose_name = meta_attrs.pop('verbose_name',
re.sub('([A-Z])', ' \\1', name).lower().strip()), re.sub('([A-Z])', ' \\1', name).lower().strip()),
verbose_name_plural = attrs.pop('verbose_name_plural', ''), verbose_name_plural = meta_attrs.pop('verbose_name_plural', ''),
db_table = attrs.pop('db_table', ''), db_table = meta_attrs.pop('db_table', ''),
fields = attrs.pop('fields'), fields = fields,
ordering = attrs.pop('ordering', None), ordering = meta_attrs.pop('ordering', None),
unique_together = attrs.pop('unique_together', None), unique_together = meta_attrs.pop('unique_together', None),
admin = attrs.pop('admin', None), admin = meta_attrs.pop('admin', None),
has_related_links = attrs.pop('has_related_links', False), has_related_links = meta_attrs.pop('has_related_links', False),
where_constraints = attrs.pop('where_constraints', None), where_constraints = meta_attrs.pop('where_constraints', None),
object_name = name, object_name = name,
app_label = attrs.pop('app_label', None), app_label = meta_attrs.pop('app_label', None),
exceptions = attrs.pop('exceptions', None), exceptions = meta_attrs.pop('exceptions', None),
permissions = attrs.pop('permissions', None), permissions = meta_attrs.pop('permissions', None),
get_latest_by = attrs.pop('get_latest_by', None), get_latest_by = meta_attrs.pop('get_latest_by', None),
order_with_respect_to = attrs.pop('order_with_respect_to', None), order_with_respect_to = meta_attrs.pop('order_with_respect_to', None),
module_constants = attrs.pop('module_constants', None), module_constants = meta_attrs.pop('module_constants', None),
) )
if meta_attrs != {}:
raise TypeError, "'class META' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())
# Dynamically create the module that will contain this class and its # Dynamically create the module that will contain this class and its
# associated helper functions. # associated helper functions.
if replaces_module is not None: if replaces_module is not None:
@ -511,7 +534,7 @@ class ModelBase(type):
# RECURSIVE_RELATIONSHIP_CONSTANT, create that relationship formally. # RECURSIVE_RELATIONSHIP_CONSTANT, create that relationship formally.
if f.rel and f.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT: if f.rel and f.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT:
f.rel.to = opts f.rel.to = opts
f.name = f.name or ((f.rel.name or f.rel.to.object_name.lower()) + '_' + f.rel.to.pk.name) f.name = f.name or (f.rel.to.object_name.lower() + '_' + f.rel.to.pk.name)
f.verbose_name = f.verbose_name or f.rel.to.verbose_name f.verbose_name = f.verbose_name or f.rel.to.verbose_name
f.rel.field_name = f.rel.field_name or f.rel.to.pk.name f.rel.field_name = f.rel.field_name or f.rel.to.pk.name
# Add "get_thingie" methods for many-to-one related objects. # Add "get_thingie" methods for many-to-one related objects.
@ -519,14 +542,14 @@ class ModelBase(type):
if isinstance(f.rel, ManyToOne): if isinstance(f.rel, ManyToOne):
func = curry(method_get_many_to_one, f) func = curry(method_get_many_to_one, f)
func.__doc__ = "Returns the associated `%s.%s` object." % (f.rel.to.app_label, f.rel.to.module_name) func.__doc__ = "Returns the associated `%s.%s` object." % (f.rel.to.app_label, f.rel.to.module_name)
attrs['get_%s' % f.rel.name] = func attrs['get_%s' % f.name] = func
for f in opts.many_to_many: for f in opts.many_to_many:
# Add "get_thingie" methods for many-to-many related objects. # Add "get_thingie" methods for many-to-many related objects.
# EXAMPLES: Poll.get_site_list(), Story.get_byline_list() # EXAMPLES: Poll.get_site_list(), Story.get_byline_list()
func = curry(method_get_many_to_many, f) func = curry(method_get_many_to_many, f)
func.__doc__ = "Returns a list of associated `%s.%s` objects." % (f.rel.to.app_label, f.rel.to.module_name) func.__doc__ = "Returns a list of associated `%s.%s` objects." % (f.rel.to.app_label, f.rel.to.module_name)
attrs['get_%s_list' % f.rel.name] = func attrs['get_%s_list' % f.rel.singular] = func
# Add "set_thingie" methods for many-to-many related objects. # Add "set_thingie" methods for many-to-many related objects.
# EXAMPLES: Poll.set_sites(), Story.set_bylines() # EXAMPLES: Poll.set_sites(), Story.set_bylines()
func = curry(method_set_many_to_many, f) func = curry(method_set_many_to_many, f)
@ -711,14 +734,36 @@ class Model:
def method_init(opts, self, *args, **kwargs): def method_init(opts, self, *args, **kwargs):
if kwargs: if kwargs:
for f in opts.fields: for f in opts.fields:
setattr(self, f.name, kwargs.pop(f.name, f.get_default())) if isinstance(f.rel, ManyToOne):
try:
# Assume object instance was passed in.
rel_obj = kwargs.pop(f.name)
except KeyError:
try:
# Object instance wasn't passed in -- must be an ID.
val = kwargs.pop(f.column)
except KeyError:
val = f.get_default()
else:
# Special case: You can pass in "None" for related objects if it's allowed.
if rel_obj is None and f.null:
val = None
else:
try:
val = getattr(rel_obj, f.rel.field_name)
except AttributeError:
raise TypeError, "Invalid value: %r should be a %s instance, not a %s" % (f.name, f.rel.to, type(rel_obj))
setattr(self, f.column, val)
else:
val = kwargs.pop(f.name, f.get_default())
setattr(self, f.name, val)
if kwargs: if kwargs:
raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0] raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]
for i, arg in enumerate(args): for i, arg in enumerate(args):
setattr(self, opts.fields[i].name, arg) setattr(self, opts.fields[i].column, arg)
def method_eq(opts, self, other): def method_eq(opts, self, other):
return isinstance(other, self.__class__) and getattr(self, opts.pk.name) == getattr(other, opts.pk.name) return isinstance(other, self.__class__) and getattr(self, opts.pk.column) == getattr(other, opts.pk.column)
def method_save(opts, self): def method_save(opts, self):
# Run any pre-save hooks. # Run any pre-save hooks.
@ -728,41 +773,41 @@ def method_save(opts, self):
cursor = db.db.cursor() cursor = db.db.cursor()
# First, try an UPDATE. If that doesn't update anything, do an INSERT. # First, try an UPDATE. If that doesn't update anything, do an INSERT.
pk_val = getattr(self, opts.pk.name) pk_val = getattr(self, opts.pk.column)
pk_set = bool(pk_val) pk_set = bool(pk_val)
record_exists = True record_exists = True
if pk_set: if pk_set:
# Determine whether a record with the primary key already exists. # Determine whether a record with the primary key already exists.
cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % (opts.db_table, opts.pk.name), [pk_val]) cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % (opts.db_table, opts.pk.column), [pk_val])
# If it does already exist, do an UPDATE. # If it does already exist, do an UPDATE.
if cursor.fetchone(): if cursor.fetchone():
db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.name), False)) for f in non_pks] db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.column), False)) for f in non_pks]
cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % (opts.db_table, cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % (opts.db_table,
','.join(['%s=%%s' % f.name for f in non_pks]), opts.pk.name), ','.join(['%s=%%s' % f.column for f in non_pks]), opts.pk.column),
db_values + [pk_val]) db_values + [pk_val])
else: else:
record_exists = False record_exists = False
if not pk_set or not record_exists: if not pk_set or not record_exists:
field_names = [f.name for f in opts.fields if not isinstance(f, AutoField)] field_names = [f.column for f in opts.fields if not isinstance(f, AutoField)]
placeholders = ['%s'] * len(field_names) placeholders = ['%s'] * len(field_names)
db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.name), True)) for f in opts.fields if not isinstance(f, AutoField)] db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.column), True)) for f in opts.fields if not isinstance(f, AutoField)]
if opts.order_with_respect_to: if opts.order_with_respect_to:
field_names.append('_order') field_names.append('_order')
# TODO: This assumes the database supports subqueries. # TODO: This assumes the database supports subqueries.
placeholders.append('(SELECT COUNT(*) FROM %s WHERE %s = %%s)' % \ placeholders.append('(SELECT COUNT(*) FROM %s WHERE %s = %%s)' % \
(opts.db_table, opts.order_with_respect_to.name)) (opts.db_table, opts.order_with_respect_to.column))
db_values.append(getattr(self, opts.order_with_respect_to.name)) db_values.append(getattr(self, opts.order_with_respect_to.column))
cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % (opts.db_table, cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % (opts.db_table,
','.join(field_names), ','.join(placeholders)), db_values) ','.join(field_names), ','.join(placeholders)), db_values)
if opts.has_auto_field: if opts.has_auto_field:
setattr(self, opts.pk.name, db.get_last_insert_id(cursor, opts.db_table, opts.pk.name)) setattr(self, opts.pk.column, db.get_last_insert_id(cursor, opts.db_table, opts.pk.column))
db.db.commit() db.db.commit()
# Run any post-save hooks. # Run any post-save hooks.
if hasattr(self, '_post_save'): if hasattr(self, '_post_save'):
self._post_save() self._post_save()
def method_delete(opts, self): def method_delete(opts, self):
assert getattr(self, opts.pk.name) is not None, "%r can't be deleted because it doesn't have an ID." assert getattr(self, opts.pk.column) is not None, "%r can't be deleted because it doesn't have an ID."
# Run any pre-delete hooks. # Run any pre-delete hooks.
if hasattr(self, '_pre_delete'): if hasattr(self, '_pre_delete'):
self._pre_delete() self._pre_delete()
@ -781,15 +826,15 @@ def method_delete(opts, self):
sub_obj.delete() sub_obj.delete()
for rel_opts, rel_field in opts.get_all_related_many_to_many_objects(): for rel_opts, rel_field in opts.get_all_related_many_to_many_objects():
cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (rel_field.get_m2m_db_table(rel_opts), cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (rel_field.get_m2m_db_table(rel_opts),
self._meta.object_name.lower()), [getattr(self, opts.pk.name)]) self._meta.object_name.lower()), [getattr(self, opts.pk.column)])
for f in opts.many_to_many: for f in opts.many_to_many:
cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (f.get_m2m_db_table(opts), self._meta.object_name.lower()), cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (f.get_m2m_db_table(opts), self._meta.object_name.lower()),
[getattr(self, opts.pk.name)]) [getattr(self, opts.pk.column)])
cursor.execute("DELETE FROM %s WHERE %s=%%s" % (opts.db_table, opts.pk.name), [getattr(self, opts.pk.name)]) cursor.execute("DELETE FROM %s WHERE %s=%%s" % (opts.db_table, opts.pk.column), [getattr(self, opts.pk.column)])
db.db.commit() db.db.commit()
setattr(self, opts.pk.name, None) setattr(self, opts.pk.column, None)
for f in opts.fields: for f in opts.fields:
if isinstance(f, FileField) and getattr(self, f.name): if isinstance(f, FileField) and getattr(self, f.column):
file_name = getattr(self, 'get_%s_filename' % f.name)() file_name = getattr(self, 'get_%s_filename' % f.name)()
# If the file exists and no other object of this type references it, # If the file exists and no other object of this type references it,
# delete it from the filesystem. # delete it from the filesystem.
@ -802,26 +847,26 @@ def method_delete(opts, self):
def method_get_next_in_order(opts, order_field, self): def method_get_next_in_order(opts, order_field, self):
if not hasattr(self, '_next_in_order_cache'): if not hasattr(self, '_next_in_order_cache'):
self._next_in_order_cache = opts.get_model_module().get_object(order_by=('_order',), self._next_in_order_cache = opts.get_model_module().get_object(order_by=('_order',),
where=['_order > (SELECT _order FROM %s WHERE %s=%%s)' % (opts.db_table, opts.pk.name), where=['_order > (SELECT _order FROM %s WHERE %s=%%s)' % (opts.db_table, opts.pk.column),
'%s=%%s' % order_field.name], limit=1, '%s=%%s' % order_field.column], limit=1,
params=[getattr(self, opts.pk.name), getattr(self, order_field.name)]) params=[getattr(self, opts.pk.column), getattr(self, order_field.name)])
return self._next_in_order_cache return self._next_in_order_cache
def method_get_previous_in_order(opts, order_field, self): def method_get_previous_in_order(opts, order_field, self):
if not hasattr(self, '_previous_in_order_cache'): if not hasattr(self, '_previous_in_order_cache'):
self._previous_in_order_cache = opts.get_model_module().get_object(order_by=('-_order',), self._previous_in_order_cache = opts.get_model_module().get_object(order_by=('-_order',),
where=['_order < (SELECT _order FROM %s WHERE %s=%%s)' % (opts.db_table, opts.pk.name), where=['_order < (SELECT _order FROM %s WHERE %s=%%s)' % (opts.db_table, opts.pk.column),
'%s=%%s' % order_field.name], limit=1, '%s=%%s' % order_field.column], limit=1,
params=[getattr(self, opts.pk.name), getattr(self, order_field.name)]) params=[getattr(self, opts.pk.column), getattr(self, order_field.name)])
return self._previous_in_order_cache return self._previous_in_order_cache
# RELATIONSHIP METHODS ##################### # RELATIONSHIP METHODS #####################
# Example: Story.get_dateline() # Example: Story.get_dateline()
def method_get_many_to_one(field_with_rel, self): def method_get_many_to_one(field_with_rel, self):
cache_var = field_with_rel.rel.get_cache_name() cache_var = field_with_rel.get_cache_name()
if not hasattr(self, cache_var): if not hasattr(self, cache_var):
val = getattr(self, field_with_rel.name) val = getattr(self, field_with_rel.column)
mod = field_with_rel.rel.to.get_model_module() mod = field_with_rel.rel.to.get_model_module()
if val is None: if val is None:
raise getattr(mod, '%sDoesNotExist' % field_with_rel.rel.to.object_name) raise getattr(mod, '%sDoesNotExist' % field_with_rel.rel.to.object_name)
@ -837,11 +882,11 @@ def method_get_many_to_many(field_with_rel, self):
if not hasattr(self, cache_var): if not hasattr(self, cache_var):
mod = rel.get_model_module() mod = rel.get_model_module()
sql = "SELECT %s FROM %s a, %s b WHERE a.%s = b.%s_id AND b.%s_id = %%s %s" % \ sql = "SELECT %s FROM %s a, %s b WHERE a.%s = b.%s_id AND b.%s_id = %%s %s" % \
(','.join(['a.%s' % f.name for f in rel.fields]), rel.db_table, (','.join(['a.%s' % f.column for f in rel.fields]), rel.db_table,
field_with_rel.get_m2m_db_table(self._meta), rel.pk.name, field_with_rel.get_m2m_db_table(self._meta), rel.pk.column,
rel.object_name.lower(), self._meta.object_name.lower(), rel.get_order_sql('a')) rel.object_name.lower(), self._meta.object_name.lower(), rel.get_order_sql('a'))
cursor = db.db.cursor() cursor = db.db.cursor()
cursor.execute(sql, [getattr(self, self._meta.pk.name)]) cursor.execute(sql, [getattr(self, self._meta.pk.column)])
setattr(self, cache_var, [getattr(mod, rel.object_name)(*row) for row in cursor.fetchall()]) setattr(self, cache_var, [getattr(mod, rel.object_name)(*row) for row in cursor.fetchall()])
return getattr(self, cache_var) return getattr(self, cache_var)
@ -863,7 +908,7 @@ def method_set_many_to_many(rel_field, self, id_list):
rel = rel_field.rel.to rel = rel_field.rel.to
m2m_table = rel_field.get_m2m_db_table(self._meta) m2m_table = rel_field.get_m2m_db_table(self._meta)
cursor = db.db.cursor() cursor = db.db.cursor()
this_id = getattr(self, self._meta.pk.name) this_id = getattr(self, self._meta.pk.column)
if ids_to_delete: if ids_to_delete:
sql = "DELETE FROM %s WHERE %s_id = %%s AND %s_id IN (%s)" % (m2m_table, self._meta.object_name.lower(), rel.object_name.lower(), ','.join(map(str, ids_to_delete))) sql = "DELETE FROM %s WHERE %s_id = %%s AND %s_id IN (%s)" % (m2m_table, self._meta.object_name.lower(), rel.object_name.lower(), ','.join(map(str, ids_to_delete)))
cursor.execute(sql, [this_id]) cursor.execute(sql, [this_id])
@ -880,7 +925,7 @@ def method_set_many_to_many(rel_field, self, id_list):
# Handles related-object retrieval. # Handles related-object retrieval.
# Examples: Poll.get_choice(), Poll.get_choice_list(), Poll.get_choice_count() # Examples: Poll.get_choice(), Poll.get_choice_list(), Poll.get_choice_count()
def method_get_related(method_name, rel_mod, rel_field, self, **kwargs): def method_get_related(method_name, rel_mod, rel_field, self, **kwargs):
kwargs['%s__exact' % rel_field.name] = getattr(self, rel_field.rel.field_name) kwargs['%s__%s__exact' % (rel_field.name, rel_field.rel.to.pk.name)] = getattr(self, rel_field.rel.field_name)
kwargs.update(rel_field.rel.lookup_overrides) kwargs.update(rel_field.rel.lookup_overrides)
return getattr(rel_mod, method_name)(**kwargs) return getattr(rel_mod, method_name)(**kwargs)
@ -892,7 +937,7 @@ def method_add_related(rel_obj, rel_mod, rel_field, self, *args, **kwargs):
for f in rel_obj.fields: for f in rel_obj.fields:
if isinstance(f, AutoField): if isinstance(f, AutoField):
init_kwargs[f.name] = None init_kwargs[f.name] = None
init_kwargs[rel_field.name] = getattr(self, rel_field.rel.field_name) init_kwargs[rel_field.name] = self
obj = rel_mod.Klass(**init_kwargs) obj = rel_mod.Klass(**init_kwargs)
obj.save() obj.save()
return obj return obj
@ -909,7 +954,7 @@ def method_set_related_many_to_many(rel_opts, rel_field, self, id_list):
id_list = map(int, id_list) # normalize to integers id_list = map(int, id_list) # normalize to integers
rel = rel_field.rel.to rel = rel_field.rel.to
m2m_table = rel_field.get_m2m_db_table(rel_opts) m2m_table = rel_field.get_m2m_db_table(rel_opts)
this_id = getattr(self, self._meta.pk.name) this_id = getattr(self, self._meta.pk.column)
cursor = db.db.cursor() cursor = db.db.cursor()
cursor.execute("DELETE FROM %s WHERE %s_id = %%s" % (m2m_table, rel.object_name.lower()), [this_id]) cursor.execute("DELETE FROM %s WHERE %s_id = %%s" % (m2m_table, rel.object_name.lower()), [this_id])
sql = "INSERT INTO %s (%s_id, %s_id) VALUES (%%s, %%s)" % (m2m_table, rel.object_name.lower(), rel_opts.object_name.lower()) sql = "INSERT INTO %s (%s_id, %s_id) VALUES (%%s, %%s)" % (m2m_table, rel.object_name.lower(), rel_opts.object_name.lower())
@ -921,7 +966,7 @@ def method_set_related_many_to_many(rel_opts, rel_field, self, id_list):
def method_set_order(ordered_obj, self, id_list): def method_set_order(ordered_obj, self, id_list):
cursor = db.db.cursor() cursor = db.db.cursor()
# Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s" # Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s"
sql = "UPDATE %s SET _order = %%s WHERE %s = %%s AND %s = %%s" % (ordered_obj.db_table, ordered_obj.order_with_respect_to.name, ordered_obj.pk.name) sql = "UPDATE %s SET _order = %%s WHERE %s = %%s AND %s = %%s" % (ordered_obj.db_table, ordered_obj.order_with_respect_to.column, ordered_obj.pk.column)
rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name) rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name)
cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)]) cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)])
db.db.commit() db.db.commit()
@ -929,7 +974,7 @@ def method_set_order(ordered_obj, self, id_list):
def method_get_order(ordered_obj, self): def method_get_order(ordered_obj, self):
cursor = db.db.cursor() cursor = db.db.cursor()
# Example: "SELECT id FROM poll_choices WHERE poll_id = %s ORDER BY _order" # Example: "SELECT id FROM poll_choices WHERE poll_id = %s ORDER BY _order"
sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY _order" % (ordered_obj.pk.name, ordered_obj.db_table, ordered_obj.order_with_respect_to.name) sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY _order" % (ordered_obj.pk.column, ordered_obj.db_table, ordered_obj.order_with_respect_to.column)
rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name) rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name)
cursor.execute(sql, [rel_val]) cursor.execute(sql, [rel_val])
return [r[0] for r in cursor.fetchall()] return [r[0] for r in cursor.fetchall()]
@ -937,7 +982,7 @@ def method_get_order(ordered_obj, self):
# DATE-RELATED METHODS ##################### # DATE-RELATED METHODS #####################
def method_get_next_or_previous(get_object_func, field, is_next, self, **kwargs): def method_get_next_or_previous(get_object_func, field, is_next, self, **kwargs):
kwargs.setdefault('where', []).append('%s %s %%s' % (field.name, (is_next and '>' or '<'))) kwargs.setdefault('where', []).append('%s %s %%s' % (field.column, (is_next and '>' or '<')))
kwargs.setdefault('params', []).append(str(getattr(self, field.name))) kwargs.setdefault('params', []).append(str(getattr(self, field.name)))
kwargs['order_by'] = [(not is_next and '-' or '') + field.name] kwargs['order_by'] = [(not is_next and '-' or '') + field.name]
kwargs['limit'] = 1 kwargs['limit'] = 1
@ -1045,7 +1090,7 @@ def _get_cached_row(opts, row, index_start):
for f in opts.fields: for f in opts.fields:
if f.rel and not f.null: if f.rel and not f.null:
rel_obj, index_end = _get_cached_row(f.rel.to, row, index_end) rel_obj, index_end = _get_cached_row(f.rel.to, row, index_end)
setattr(obj, f.rel.get_cache_name(), rel_obj) setattr(obj, f.get_cache_name(), rel_obj)
return obj, index_end return obj, index_end
def function_get_iterator(opts, klass, **kwargs): def function_get_iterator(opts, klass, **kwargs):
@ -1091,9 +1136,9 @@ def function_get_values_iterator(opts, klass, **kwargs):
# 'fields' is a list of field names to fetch. # 'fields' is a list of field names to fetch.
try: try:
fields = kwargs.pop('fields') fields = [opts.get_field(f).column for f in kwargs.pop('fields')]
except KeyError: # Default to all fields. except KeyError: # Default to all fields.
fields = [f.name for f in opts.fields] fields = [f.column for f in opts.fields]
cursor = db.db.cursor() cursor = db.db.cursor()
_, sql, params = function_get_sql_clause(opts, **kwargs) _, sql, params = function_get_sql_clause(opts, **kwargs)
@ -1124,8 +1169,8 @@ def _fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen
tables.append('%s %s' % (db_table, new_prefix)) tables.append('%s %s' % (db_table, new_prefix))
db_table = new_prefix db_table = new_prefix
cache_tables_seen.append(db_table) cache_tables_seen.append(db_table)
where.append('%s.%s = %s.%s' % (old_prefix, f.name, db_table, f.rel.field_name)) where.append('%s.%s = %s.%s' % (old_prefix, f.column, db_table, f.rel.get_related_field().column))
select.extend(['%s.%s' % (db_table, f2.name) for f2 in f.rel.to.fields]) select.extend(['%s.%s' % (db_table, f2.column) for f2 in f.rel.to.fields])
_fill_table_cache(f.rel.to, select, tables, where, db_table, cache_tables_seen) _fill_table_cache(f.rel.to, select, tables, where, db_table, cache_tables_seen)
def _throw_bad_kwarg_error(kwarg): def _throw_bad_kwarg_error(kwarg):
@ -1158,7 +1203,10 @@ def _parse_lookup(kwarg_items, opts, table_count=0):
lookup_list = kwarg.split(LOOKUP_SEPARATOR) lookup_list = kwarg.split(LOOKUP_SEPARATOR)
# pk="value" is shorthand for (primary key)__exact="value" # pk="value" is shorthand for (primary key)__exact="value"
if lookup_list[-1] == 'pk': if lookup_list[-1] == 'pk':
lookup_list = lookup_list[:-1] + [opts.pk.name, 'exact'] if opts.pk.rel:
lookup_list = lookup_list[:-1] + [opts.pk.name, opts.pk.rel.field_name, 'exact']
else:
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()
@ -1184,7 +1232,7 @@ def _parse_lookup(kwarg_items, opts, table_count=0):
rel_table_alias = 't%s' % table_count rel_table_alias = 't%s' % table_count
table_count += 1 table_count += 1
tables.append('%s %s' % (f.get_m2m_db_table(current_opts), rel_table_alias)) tables.append('%s %s' % (f.get_m2m_db_table(current_opts), rel_table_alias))
join_where.append('%s.%s = %s.%s_id' % (current_table_alias, current_opts.pk.name, join_where.append('%s.%s = %s.%s_id' % (current_table_alias, current_opts.pk.column,
rel_table_alias, current_opts.object_name.lower())) rel_table_alias, current_opts.object_name.lower()))
# Optimization: In the case of primary-key lookups, we # Optimization: In the case of primary-key lookups, we
# don't have to do an extra join. # don't have to do an extra join.
@ -1198,32 +1246,39 @@ def _parse_lookup(kwarg_items, opts, table_count=0):
new_table_alias = 't%s' % table_count new_table_alias = 't%s' % table_count
tables.append('%s %s' % (f.rel.to.db_table, new_table_alias)) tables.append('%s %s' % (f.rel.to.db_table, new_table_alias))
join_where.append('%s.%s_id = %s.%s' % (rel_table_alias, f.rel.to.object_name.lower(), join_where.append('%s.%s_id = %s.%s' % (rel_table_alias, f.rel.to.object_name.lower(),
new_table_alias, f.rel.to.pk.name)) new_table_alias, f.rel.to.pk.column))
current_table_alias = new_table_alias current_table_alias = new_table_alias
param_required = True param_required = True
current_opts = f.rel.to current_opts = f.rel.to
raise StopIteration raise StopIteration
for f in current_opts.fields: for f in current_opts.fields:
# Try many-to-one relationships... # Try many-to-one relationships...
if f.rel and f.rel.name == current: if f.rel and f.name == current:
# Optimization: In the case of primary-key lookups, we # Optimization: In the case of primary-key lookups, we
# don't have to do an extra join. # don't have to do an extra join.
if lookup_list and lookup_list[0] == f.rel.to.pk.name and lookup_type == 'exact': if lookup_list and lookup_list[0] == f.rel.to.pk.name and lookup_type == 'exact':
where.append(_get_where_clause(lookup_type, current_table_alias+'.', f.name, kwarg_value)) where.append(_get_where_clause(lookup_type, current_table_alias+'.', f.column, kwarg_value))
params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value)) params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value))
lookup_list.pop() lookup_list.pop()
param_required = False param_required = False
# 'isnull' lookups in many-to-one relationships are a special case,
# because we don't want to do a join. We just want to find out
# whether the foreign key field is NULL.
elif lookup_type == 'isnull' and not lookup_list:
where.append(_get_where_clause(lookup_type, current_table_alias+'.', f.column, kwarg_value))
params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value))
else: else:
new_table_alias = 't%s' % table_count new_table_alias = 't%s' % table_count
tables.append('%s %s' % (f.rel.to.db_table, new_table_alias)) tables.append('%s %s' % (f.rel.to.db_table, new_table_alias))
join_where.append('%s.%s = %s.%s' % (current_table_alias, f.name, new_table_alias, f.rel.to.pk.name)) join_where.append('%s.%s = %s.%s' % (current_table_alias, f.column, \
new_table_alias, f.rel.to.pk.column))
current_table_alias = new_table_alias current_table_alias = new_table_alias
param_required = True param_required = True
current_opts = f.rel.to current_opts = f.rel.to
raise StopIteration raise StopIteration
# Try direct field-name lookups... # Try direct field-name lookups...
if f.name == current: if f.name == current:
where.append(_get_where_clause(lookup_type, current_table_alias+'.', current, kwarg_value)) where.append(_get_where_clause(lookup_type, current_table_alias+'.', f.column, kwarg_value))
params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value)) params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value))
param_required = False param_required = False
raise StopIteration raise StopIteration
@ -1235,7 +1290,7 @@ def _parse_lookup(kwarg_items, opts, table_count=0):
return tables, join_where, where, params, table_count return tables, join_where, where, params, table_count
def function_get_sql_clause(opts, **kwargs): def function_get_sql_clause(opts, **kwargs):
select = ["%s.%s" % (opts.db_table, f.name) for f in opts.fields] select = ["%s.%s" % (opts.db_table, f.column) for f in opts.fields]
tables = [opts.db_table] + (kwargs.get('tables') and kwargs['tables'][:] or []) tables = [opts.db_table] + (kwargs.get('tables') and kwargs['tables'][:] or [])
where = kwargs.get('where') and kwargs['where'][:] or [] where = kwargs.get('where') and kwargs['where'][:] or []
params = kwargs.get('params') and kwargs['params'][:] or [] params = kwargs.get('params') and kwargs['params'][:] or []
@ -1270,9 +1325,9 @@ def function_get_sql_clause(opts, **kwargs):
else: else:
table_prefix = '' table_prefix = ''
if f.startswith('-'): if f.startswith('-'):
order_by.append('%s%s DESC' % (table_prefix, f[1:])) order_by.append('%s%s DESC' % (table_prefix, orderfield2column(f[1:], opts)))
else: else:
order_by.append('%s%s ASC' % (table_prefix, f)) order_by.append('%s%s ASC' % (table_prefix, orderfield2column(f, opts)))
order_by = ", ".join(order_by) order_by = ", ".join(order_by)
# LIMIT and OFFSET clauses # LIMIT and OFFSET clauses
@ -1307,9 +1362,9 @@ def function_get_date_list(opts, field, *args, **kwargs):
assert order in ('ASC', 'DESC'), "'order' must be either 'ASC' or 'DESC'" assert order in ('ASC', 'DESC'), "'order' must be either 'ASC' or 'DESC'"
kwargs['order_by'] = [] # Clear this because it'll mess things up otherwise. kwargs['order_by'] = [] # Clear this because it'll mess things up otherwise.
if field.null: if field.null:
kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % (opts.db_table, field.name)) kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % (opts.db_table, field.column))
select, sql, params = function_get_sql_clause(opts, **kwargs) select, sql, params = function_get_sql_clause(opts, **kwargs)
sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % (db.get_date_trunc_sql(kind, '%s.%s' % (opts.db_table, field.name)), sql) sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % (db.get_date_trunc_sql(kind, '%s.%s' % (opts.db_table, field.column)), sql)
cursor = db.db.cursor() cursor = db.db.cursor()
cursor.execute(sql, params) cursor.execute(sql, params)
# We have to manually run typecast_timestamp(str()) on the results, because # We have to manually run typecast_timestamp(str()) on the results, because
@ -1359,8 +1414,8 @@ def manipulator_init(opts, add, change, self, obj_key=None):
lookup_kwargs = opts.one_to_one_field.rel.limit_choices_to lookup_kwargs = opts.one_to_one_field.rel.limit_choices_to
lookup_kwargs['%s__exact' % opts.one_to_one_field.rel.field_name] = obj_key lookup_kwargs['%s__exact' % opts.one_to_one_field.rel.field_name] = obj_key
_ = opts.one_to_one_field.rel.to.get_model_module().get_object(**lookup_kwargs) _ = opts.one_to_one_field.rel.to.get_model_module().get_object(**lookup_kwargs)
params = dict([(f.name, f.get_default()) for f in opts.fields]) params = dict([(f.column, f.get_default()) for f in opts.fields])
params[opts.pk.name] = obj_key params[opts.pk.column] = obj_key
self.original_object = opts.get_model_module().Klass(**params) self.original_object = opts.get_model_module().Klass(**params)
else: else:
raise raise
@ -1396,9 +1451,9 @@ def manipulator_save(opts, klass, add, change, self, new_data):
# Fields with auto_now_add are another special case; they should keep # Fields with auto_now_add are another special case; they should keep
# their original value in the change stage. # their original value in the change stage.
if change and getattr(f, 'auto_now_add', False): if change and getattr(f, 'auto_now_add', False):
params[f.name] = getattr(self.original_object, f.name) params[f.column] = getattr(self.original_object, f.name)
else: else:
params[f.name] = f.get_manipulator_new_data(new_data) params[f.column] = f.get_manipulator_new_data(new_data)
if change: if change:
params[opts.pk.name] = self.obj_key params[opts.pk.name] = self.obj_key
@ -1416,7 +1471,7 @@ def manipulator_save(opts, klass, add, change, self, new_data):
if change: if change:
self.fields_added, self.fields_changed, self.fields_deleted = [], [], [] self.fields_added, self.fields_changed, self.fields_deleted = [], [], []
for f in opts.fields: for f in opts.fields:
if not f.primary_key and str(getattr(self.original_object, f.name)) != str(getattr(new_object, f.name)): if not f.primary_key and str(getattr(self.original_object, f.column)) != str(getattr(new_object, f.column)):
self.fields_changed.append(f.verbose_name) self.fields_changed.append(f.verbose_name)
# Save many-to-many objects. Example: Poll.set_sites() # Save many-to-many objects. Example: Poll.set_sites()
@ -1467,15 +1522,15 @@ def manipulator_save(opts, klass, add, change, self, new_data):
# case, because they'll be dealt with later. # case, because they'll be dealt with later.
if change and (isinstance(f, FileField) or not f.editable): if change and (isinstance(f, FileField) or not f.editable):
if rel_new_data.get(rel_opts.pk.name, False) and rel_new_data[rel_opts.pk.name][0]: if rel_new_data.get(rel_opts.pk.name, False) and rel_new_data[rel_opts.pk.name][0]:
params[f.name] = getattr(old_rel_obj, f.name) params[f.column] = getattr(old_rel_obj, f.column)
else: else:
params[f.name] = f.get_default() params[f.column] = f.get_default()
elif f == rel_field: elif f == rel_field:
params[f.name] = getattr(new_object, rel_field.rel.field_name) params[f.column] = getattr(new_object, rel_field.rel.field_name)
elif add and isinstance(f, AutoField): elif add and isinstance(f, AutoField):
params[f.name] = None params[f.column] = None
else: else:
params[f.name] = f.get_manipulator_new_data(rel_new_data, rel=True) params[f.column] = f.get_manipulator_new_data(rel_new_data, rel=True)
# Related links are a special case, because we have to # Related links are a special case, because we have to
# manually set the "content_type_id" field. # manually set the "content_type_id" field.
if opts.has_related_links and rel_opts.module_name == 'relatedlinks': if opts.has_related_links and rel_opts.module_name == 'relatedlinks':
@ -1501,7 +1556,7 @@ def manipulator_save(opts, klass, add, change, self, new_data):
self.fields_added.append('%s "%r"' % (rel_opts.verbose_name, new_rel_obj)) self.fields_added.append('%s "%r"' % (rel_opts.verbose_name, new_rel_obj))
else: else:
for f in rel_opts.fields: for f in rel_opts.fields:
if not f.primary_key and f != rel_field and str(getattr(old_rel_obj, f.name)) != str(getattr(new_rel_obj, f.name)): if not f.primary_key and f != rel_field and str(getattr(old_rel_obj, f.column)) != str(getattr(new_rel_obj, f.column)):
self.fields_changed.append('%s for %s "%r"' % (f.verbose_name, rel_opts.verbose_name, new_rel_obj)) self.fields_changed.append('%s for %s "%r"' % (f.verbose_name, rel_opts.verbose_name, new_rel_obj))
# Save many-to-many objects. # Save many-to-many objects.
@ -1527,20 +1582,26 @@ def manipulator_save(opts, klass, add, change, self, new_data):
def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data): def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data):
from django.utils.text import get_text_list from django.utils.text import get_text_list
field_list = [opts.get_field(field_name) for field_name in field_name_list] field_list = [opts.get_field(field_name) for field_name in field_name_list]
kwargs = {'%s__iexact' % field_name_list[0]: field_data} if isinstance(field_list[0].rel, ManyToOne):
kwargs = {'%s__%s__iexact' % (field_name_list[0], field_list[0].rel.field_name): field_data}
else:
kwargs = {'%s__iexact' % field_name_list[0]: field_data}
for f in field_list[1:]: for f in field_list[1:]:
field_val = all_data.get(f.name, None) field_val = all_data.get(f.column, None)
if field_val is None: if field_val is None:
# This will be caught by another validator, assuming the field # This will be caught by another validator, assuming the field
# doesn't have blank=True. # doesn't have blank=True.
return return
kwargs['%s__iexact' % f.name] = field_val if isinstance(f.rel, ManyToOne):
kwargs['%s__pk' % f.name] = field_val
else:
kwargs['%s__iexact' % f.name] = field_val
mod = opts.get_model_module() mod = opts.get_model_module()
try: try:
old_obj = mod.get_object(**kwargs) old_obj = mod.get_object(**kwargs)
except ObjectDoesNotExist: except ObjectDoesNotExist:
return return
if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.name) == getattr(old_obj, opts.pk.name): if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.column) == getattr(old_obj, opts.pk.column):
pass pass
else: else:
raise validators.ValidationError, "%s with this %s already exists for the given %s." % \ raise validators.ValidationError, "%s with this %s already exists for the given %s." % \
@ -1562,7 +1623,7 @@ def manipulator_validator_unique_for_date(from_field, date_field, opts, lookup_t
except ObjectDoesNotExist: except ObjectDoesNotExist:
return return
else: else:
if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.name) == getattr(old_obj, opts.pk.name): if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.column) == getattr(old_obj, opts.pk.column):
pass pass
else: else:
format_string = (lookup_type == 'date') and '%B %d, %Y' or '%B %Y' format_string = (lookup_type == 'date') and '%B %d, %Y' or '%B %Y'

View File

@ -40,7 +40,7 @@ def manipulator_validator_unique(f, opts, self, field_data, all_data):
old_obj = opts.get_model_module().get_object(**{'%s__exact' % f.name: field_data}) old_obj = opts.get_model_module().get_object(**{'%s__exact' % f.name: field_data})
except ObjectDoesNotExist: except ObjectDoesNotExist:
return return
if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.name) == getattr(old_obj, opts.pk.name): if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.column) == getattr(old_obj, opts.pk.column):
return return
raise validators.ValidationError, "%s with this %s already exists." % (capfirst(opts.verbose_name), f.verbose_name) raise validators.ValidationError, "%s with this %s already exists." % (capfirst(opts.verbose_name), f.verbose_name)
@ -50,14 +50,17 @@ class Field(object):
# database level. # database level.
empty_strings_allowed = True empty_strings_allowed = True
def __init__(self, name, verbose_name=None, primary_key=False, # Tracks each time a Field instance is created. Used to retain order.
creation_counter = 0
def __init__(self, verbose_name=None, name=None, primary_key=False,
maxlength=None, unique=False, blank=False, null=False, db_index=None, maxlength=None, unique=False, blank=False, null=False, db_index=None,
core=False, rel=None, default=NOT_PROVIDED, editable=True, core=False, rel=None, default=NOT_PROVIDED, editable=True,
prepopulate_from=None, unique_for_date=None, unique_for_month=None, prepopulate_from=None, unique_for_date=None, unique_for_month=None,
unique_for_year=None, validator_list=None, choices=None, radio_admin=None, unique_for_year=None, validator_list=None, choices=None, radio_admin=None,
help_text=''): help_text='', db_column=None):
self.name = name self.name = name
self.verbose_name = verbose_name or name.replace('_', ' ') self.verbose_name = verbose_name or (name and name.replace('_', ' '))
self.primary_key = primary_key self.primary_key = primary_key
self.maxlength, self.unique = maxlength, unique self.maxlength, self.unique = maxlength, unique
self.blank, self.null = blank, null self.blank, self.null = blank, null
@ -70,6 +73,7 @@ class Field(object):
self.choices = choices or [] self.choices = choices or []
self.radio_admin = radio_admin self.radio_admin = radio_admin
self.help_text = help_text self.help_text = help_text
self.db_column = db_column
if rel and isinstance(rel, ManyToMany): if rel and isinstance(rel, ManyToMany):
if rel.raw_id_admin: if rel.raw_id_admin:
self.help_text += ' Separate multiple IDs with commas.' self.help_text += ' Separate multiple IDs with commas.'
@ -85,6 +89,27 @@ class Field(object):
else: else:
self.db_index = db_index self.db_index = db_index
# Increase the creation counter, and save our local copy.
self.creation_counter = Field.creation_counter
Field.creation_counter += 1
# Set the name of the database column.
self.column = self.get_db_column()
def set_name(self, name):
self.name = name
self.verbose_name = self.verbose_name or name.replace('_', ' ')
self.column = self.get_db_column()
def get_db_column(self):
if self.db_column: return self.db_column
if isinstance(self.rel, ManyToOne):
return '%s_id' % self.name
return self.name
def get_cache_name(self):
return '_%s_cache' % self.name
def pre_save(self, value, add): def pre_save(self, value, add):
"Returns field's value just before saving." "Returns field's value just before saving."
return value return value
@ -232,7 +257,7 @@ class Field(object):
if self.choices: if self.choices:
return first_choice + list(self.choices) return first_choice + list(self.choices)
rel_obj = self.rel.to rel_obj = self.rel.to
return first_choice + [(getattr(x, rel_obj.pk.name), repr(x)) for x in rel_obj.get_model_module().get_list(**self.rel.limit_choices_to)] return first_choice + [(getattr(x, rel_obj.pk.column), repr(x)) for x in rel_obj.get_model_module().get_list(**self.rel.limit_choices_to)]
class AutoField(Field): class AutoField(Field):
empty_strings_allowed = False empty_strings_allowed = False
@ -271,11 +296,11 @@ class CommaSeparatedIntegerField(CharField):
class DateField(Field): class DateField(Field):
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, name, verbose_name=None, auto_now=False, auto_now_add=False, **kwargs): def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
self.auto_now, self.auto_now_add = auto_now, auto_now_add self.auto_now, self.auto_now_add = auto_now, auto_now_add
if auto_now or auto_now_add: if auto_now or auto_now_add:
kwargs['editable'] = False kwargs['editable'] = False
Field.__init__(self, name, verbose_name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value):
if lookup_type == 'range': if lookup_type == 'range':
@ -332,9 +357,9 @@ class EmailField(Field):
return [formfields.EmailField] return [formfields.EmailField]
class FileField(Field): class FileField(Field):
def __init__(self, name, verbose_name=None, upload_to='', **kwargs): def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs):
self.upload_to = upload_to self.upload_to = upload_to
Field.__init__(self, name, verbose_name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False): def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False):
field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel) field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel)
@ -397,17 +422,17 @@ class FileField(Field):
class FloatField(Field): class FloatField(Field):
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, name, verbose_name=None, max_digits=None, decimal_places=None, **kwargs): def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs):
self.max_digits, self.decimal_places = max_digits, decimal_places self.max_digits, self.decimal_places = max_digits, decimal_places
Field.__init__(self, name, verbose_name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [curry(formfields.FloatField, max_digits=self.max_digits, decimal_places=self.decimal_places)] return [curry(formfields.FloatField, max_digits=self.max_digits, decimal_places=self.decimal_places)]
class ImageField(FileField): class ImageField(FileField):
def __init__(self, name, verbose_name=None, width_field=None, height_field=None, **kwargs): def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):
self.width_field, self.height_field = width_field, height_field self.width_field, self.height_field = width_field, height_field
FileField.__init__(self, name, verbose_name, **kwargs) FileField.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [formfields.ImageUploadField, formfields.HiddenField] return [formfields.ImageUploadField, formfields.HiddenField]
@ -479,11 +504,11 @@ class TextField(Field):
class TimeField(Field): class TimeField(Field):
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, name, verbose_name=None, auto_now=False, auto_now_add=False, **kwargs): def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
self.auto_now, self.auto_now_add = auto_now, auto_now_add self.auto_now, self.auto_now_add = auto_now, auto_now_add
if auto_now or auto_now_add: if auto_now or auto_now_add:
kwargs['editable'] = False kwargs['editable'] = False
Field.__init__(self, name, verbose_name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value):
if lookup_type == 'range': if lookup_type == 'range':
@ -511,10 +536,10 @@ class TimeField(Field):
return [formfields.TimeField] return [formfields.TimeField]
class URLField(Field): class URLField(Field):
def __init__(self, name, verbose_name=None, verify_exists=True, **kwargs): def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
if verify_exists: if verify_exists:
kwargs.setdefault('validator_list', []).append(validators.isExistingURL) kwargs.setdefault('validator_list', []).append(validators.isExistingURL)
Field.__init__(self, name, verbose_name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [formfields.URLField] return [formfields.URLField]
@ -524,34 +549,31 @@ class USStateField(Field):
return [formfields.USStateField] return [formfields.USStateField]
class XMLField(Field): class XMLField(Field):
def __init__(self, name, verbose_name=None, schema_path=None, **kwargs): def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
self.schema_path = schema_path self.schema_path = schema_path
Field.__init__(self, name, verbose_name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [curry(formfields.XMLLargeTextField, schema_path=self.schema_path)] return [curry(formfields.XMLLargeTextField, schema_path=self.schema_path)]
class ForeignKey(Field): class ForeignKey(Field):
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, to, to_field=None, rel_name=None, **kwargs): def __init__(self, to, to_field=None, **kwargs):
try: try:
to_name = to._meta.object_name.lower() to_name = to._meta.object_name.lower()
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
assert to == 'self', "ForeignKey(%r) is invalid. First parameter to ForeignKey must be either a model or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT) assert to == 'self', "ForeignKey(%r) is invalid. First parameter to ForeignKey must be either a model or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT)
kwargs['name'] = kwargs.get('name', '')
kwargs['verbose_name'] = kwargs.get('verbose_name', '') kwargs['verbose_name'] = kwargs.get('verbose_name', '')
else: else:
to_field = to_field or to._meta.pk.name to_field = to_field or to._meta.pk.name
kwargs['name'] = kwargs.get('name', to_name + '_id')
kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name) kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name)
rel_name = rel_name or to_name
if kwargs.has_key('edit_inline_type'): if kwargs.has_key('edit_inline_type'):
import warnings import warnings
warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.") warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
kwargs['edit_inline'] = kwargs.pop('edit_inline_type') kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
kwargs['rel'] = ManyToOne(to, rel_name, to_field, kwargs['rel'] = ManyToOne(to, to_field,
num_in_admin=kwargs.pop('num_in_admin', 3), num_in_admin=kwargs.pop('num_in_admin', 3),
min_num_in_admin=kwargs.pop('min_num_in_admin', None), min_num_in_admin=kwargs.pop('min_num_in_admin', None),
max_num_in_admin=kwargs.pop('max_num_in_admin', None), max_num_in_admin=kwargs.pop('max_num_in_admin', None),
@ -567,11 +589,9 @@ class ForeignKey(Field):
return [formfields.IntegerField] return [formfields.IntegerField]
class ManyToManyField(Field): class ManyToManyField(Field):
def __init__(self, to, rel_name=None, **kwargs): def __init__(self, to, **kwargs):
kwargs['name'] = kwargs.get('name', to._meta.module_name)
kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural) kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural)
rel_name = rel_name or to._meta.object_name.lower() kwargs['rel'] = ManyToMany(to, kwargs.pop('singular', None),
kwargs['rel'] = ManyToMany(to, rel_name,
num_in_admin=kwargs.pop('num_in_admin', 0), num_in_admin=kwargs.pop('num_in_admin', 0),
related_name=kwargs.pop('related_name', None), related_name=kwargs.pop('related_name', None),
filter_interface=kwargs.pop('filter_interface', None), filter_interface=kwargs.pop('filter_interface', None),
@ -609,18 +629,16 @@ class ManyToManyField(Field):
len(badkeys) == 1 and "is" or "are") len(badkeys) == 1 and "is" or "are")
class OneToOneField(IntegerField): class OneToOneField(IntegerField):
def __init__(self, to, to_field=None, rel_name=None, **kwargs): def __init__(self, to, to_field=None, **kwargs):
kwargs['name'] = kwargs.get('name', 'id')
kwargs['verbose_name'] = kwargs.get('verbose_name', 'ID') kwargs['verbose_name'] = kwargs.get('verbose_name', 'ID')
to_field = to_field or to._meta.pk.name to_field = to_field or to._meta.pk.name
rel_name = rel_name or to._meta.object_name.lower()
if kwargs.has_key('edit_inline_type'): if kwargs.has_key('edit_inline_type'):
import warnings import warnings
warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.") warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
kwargs['edit_inline'] = kwargs.pop('edit_inline_type') kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
kwargs['rel'] = OneToOne(to, rel_name, to_field, kwargs['rel'] = OneToOne(to, to_field,
num_in_admin=kwargs.pop('num_in_admin', 0), num_in_admin=kwargs.pop('num_in_admin', 0),
edit_inline=kwargs.pop('edit_inline', False), edit_inline=kwargs.pop('edit_inline', False),
related_name=kwargs.pop('related_name', None), related_name=kwargs.pop('related_name', None),
@ -631,7 +649,7 @@ class OneToOneField(IntegerField):
IntegerField.__init__(self, **kwargs) IntegerField.__init__(self, **kwargs)
class ManyToOne: class ManyToOne:
def __init__(self, to, name, field_name, num_in_admin=3, min_num_in_admin=None, def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
max_num_in_admin=None, num_extra_on_change=1, edit_inline=False, max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
related_name=None, limit_choices_to=None, lookup_overrides=None, raw_id_admin=False): related_name=None, limit_choices_to=None, lookup_overrides=None, raw_id_admin=False):
try: try:
@ -639,7 +657,7 @@ class ManyToOne:
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
assert to == RECURSIVE_RELATIONSHIP_CONSTANT, "'to' must be either a model or the string '%s'" % RECURSIVE_RELATIONSHIP_CONSTANT assert to == RECURSIVE_RELATIONSHIP_CONSTANT, "'to' must be either a model or the string '%s'" % RECURSIVE_RELATIONSHIP_CONSTANT
self.to = to self.to = to
self.name, self.field_name = name, field_name self.field_name = field_name
self.num_in_admin, self.edit_inline = num_in_admin, edit_inline self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin
self.num_extra_on_change, self.related_name = num_extra_on_change, related_name self.num_extra_on_change, self.related_name = num_extra_on_change, related_name
@ -647,17 +665,15 @@ class ManyToOne:
self.lookup_overrides = lookup_overrides or {} self.lookup_overrides = lookup_overrides or {}
self.raw_id_admin = raw_id_admin self.raw_id_admin = raw_id_admin
def get_cache_name(self):
return '_%s_cache' % self.name
def get_related_field(self): def get_related_field(self):
"Returns the Field in the 'to' object to which this relationship is tied." "Returns the Field in the 'to' object to which this relationship is tied."
return self.to.get_field(self.field_name) return self.to.get_field(self.field_name)
class ManyToMany: class ManyToMany:
def __init__(self, to, name, num_in_admin=0, related_name=None, def __init__(self, to, singular=None, num_in_admin=0, related_name=None,
filter_interface=None, limit_choices_to=None, raw_id_admin=False): filter_interface=None, limit_choices_to=None, raw_id_admin=False):
self.to, self.name = to._meta, name self.to = to._meta
self.singular = singular or to._meta.object_name.lower()
self.num_in_admin = num_in_admin self.num_in_admin = num_in_admin
self.related_name = related_name self.related_name = related_name
self.filter_interface = filter_interface self.filter_interface = filter_interface
@ -667,10 +683,10 @@ class ManyToMany:
assert not (self.raw_id_admin and self.filter_interface), "ManyToMany relationships may not use both raw_id_admin and filter_interface" assert not (self.raw_id_admin and self.filter_interface), "ManyToMany relationships may not use both raw_id_admin and filter_interface"
class OneToOne(ManyToOne): class OneToOne(ManyToOne):
def __init__(self, to, name, field_name, num_in_admin=0, edit_inline=False, def __init__(self, to, field_name, num_in_admin=0, edit_inline=False,
related_name=None, limit_choices_to=None, lookup_overrides=None, related_name=None, limit_choices_to=None, lookup_overrides=None,
raw_id_admin=False): raw_id_admin=False):
self.to, self.name, self.field_name = to._meta, name, field_name self.to, self.field_name = to._meta, field_name
self.num_in_admin, self.edit_inline = num_in_admin, edit_inline self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
self.related_name = related_name self.related_name = related_name
self.limit_choices_to = limit_choices_to or {} self.limit_choices_to = limit_choices_to or {}

View File

@ -2,65 +2,60 @@ from django.core import meta, validators
from django.models import core from django.models import core
class Permission(meta.Model): class Permission(meta.Model):
fields = ( name = meta.CharField(maxlength=50)
meta.CharField('name', maxlength=50), package = meta.ForeignKey(core.Package, db_column='package')
meta.ForeignKey(core.Package, name='package'), codename = meta.CharField(maxlength=100)
meta.CharField('codename', maxlength=100), class META:
) unique_together = (('package', 'codename'),)
unique_together = (('package', 'codename'),) ordering = ('package', 'codename')
ordering = ('package', 'codename')
def __repr__(self): def __repr__(self):
return "%s | %s" % (self.package, self.name) return "%s | %s" % (self.package, self.name)
class Group(meta.Model): class Group(meta.Model):
fields = ( name = meta.CharField(maxlength=80, unique=True)
meta.CharField('name', maxlength=80, unique=True), permissions = meta.ManyToManyField(Permission, blank=True, filter_interface=meta.HORIZONTAL)
meta.ManyToManyField(Permission, blank=True, filter_interface=meta.HORIZONTAL), class META:
) ordering = ('name',)
ordering = ('name',) admin = meta.Admin(
admin = meta.Admin( search_fields = ('name',),
search_fields = ('name',), )
)
def __repr__(self): def __repr__(self):
return self.name return self.name
class User(meta.Model): class User(meta.Model):
fields = ( username = meta.CharField(maxlength=30, unique=True, validator_list=[validators.isAlphaNumeric])
meta.CharField('username', maxlength=30, unique=True, first_name = meta.CharField(maxlength=30, blank=True)
validator_list=[validators.isAlphaNumeric]), last_name = meta.CharField(maxlength=30, blank=True)
meta.CharField('first_name', maxlength=30, blank=True), email = meta.EmailField('e-mail address', blank=True)
meta.CharField('last_name', maxlength=30, blank=True), password_md5 = meta.CharField('password', maxlength=32, help_text="Use an MD5 hash -- not the raw password.")
meta.EmailField('email', 'e-mail address', blank=True), is_staff = meta.BooleanField('staff status', help_text="Designates whether the user can log into this admin site.")
meta.CharField('password_md5', 'password', maxlength=32, help_text="Use an MD5 hash -- not the raw password."), is_active = meta.BooleanField('active', default=True)
meta.BooleanField('is_staff', 'staff status', is_superuser = meta.BooleanField('superuser status')
help_text="Designates whether the user can log into this admin site."), last_login = meta.DateTimeField(default=meta.LazyDate())
meta.BooleanField('is_active', 'active', default=True), date_joined = meta.DateTimeField(default=meta.LazyDate())
meta.BooleanField('is_superuser', 'superuser status'), groups = meta.ManyToManyField(Group, blank=True,
meta.DateTimeField('last_login', default=meta.LazyDate()), help_text="In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in.")
meta.DateTimeField('date_joined', default=meta.LazyDate()), user_permissions = meta.ManyToManyField(Permission, blank=True, filter_interface=meta.HORIZONTAL)
meta.ManyToManyField(Group, blank=True, class META:
help_text="In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in."), module_constants = {
meta.ManyToManyField(Permission, name='user_permissions', blank=True, filter_interface=meta.HORIZONTAL), 'SESSION_KEY': '_auth_user_id',
) }
module_constants = { ordering = ('username',)
'SESSION_KEY': '_auth_user_id', exceptions = ('SiteProfileNotAvailable',)
} admin = meta.Admin(
ordering = ('username',) fields = (
exceptions = ('SiteProfileNotAvailable',) (None, {'fields': ('username', 'password_md5')}),
admin = meta.Admin( ('Personal info', {'fields': ('first_name', 'last_name', 'email')}),
fields = ( ('Permissions', {'fields': ('is_staff', 'is_active', 'is_superuser', 'user_permissions')}),
(None, {'fields': ('username', 'password_md5')}), ('Important dates', {'fields': ('last_login', 'date_joined')}),
('Personal info', {'fields': ('first_name', 'last_name', 'email')}), ('Groups', {'fields': ('groups',)}),
('Permissions', {'fields': ('is_staff', 'is_active', 'is_superuser', 'user_permissions')}), ),
('Important dates', {'fields': ('last_login', 'date_joined')}), list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff'),
('Groups', {'fields': ('groups',)}), list_filter = ('is_staff', 'is_superuser'),
), search_fields = ('username', 'first_name', 'last_name', 'email'),
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff'), )
list_filter = ('is_staff', 'is_superuser'),
search_fields = ('username', 'first_name', 'last_name', 'email'),
)
def __repr__(self): def __repr__(self):
return self.username return self.username
@ -154,7 +149,7 @@ class User(meta.Model):
except ImportError: except ImportError:
try: try:
module = __import__('django.models.%s' % AUTH_PROFILE_MODULE, [], [], ['']) module = __import__('django.models.%s' % AUTH_PROFILE_MODULE, [], [], [''])
self._profile_cache = module.get_object(user_id__exact=self.id) self._profile_cache = module.get_object(user__id__exact=self.id)
except ImportError: except ImportError:
raise SiteProfileNotAvailable raise SiteProfileNotAvailable
return self._profile_cache return self._profile_cache
@ -176,33 +171,30 @@ class User(meta.Model):
return ''.join([choice(allowed_chars) for i in range(length)]) return ''.join([choice(allowed_chars) for i in range(length)])
class Message(meta.Model): class Message(meta.Model):
fields = ( user = meta.ForeignKey(User)
meta.ForeignKey(User), message = meta.TextField()
meta.TextField('message'),
)
def __repr__(self): def __repr__(self):
return self.message return self.message
class LogEntry(meta.Model): class LogEntry(meta.Model):
module_name = 'log' action_time = meta.DateTimeField(auto_now=True)
verbose_name_plural = 'log entries' user = meta.ForeignKey(User)
db_table = 'auth_admin_log' content_type = meta.ForeignKey(core.ContentType, blank=True, null=True) # TODO: content_type_id name?
fields = ( object_id = meta.TextField(blank=True, null=True)
meta.DateTimeField('action_time', auto_now=True), object_repr = meta.CharField(maxlength=200)
meta.ForeignKey(User), action_flag = meta.PositiveSmallIntegerField()
meta.ForeignKey(core.ContentType, name='content_type_id', rel_name='content_type', blank=True, null=True), change_message = meta.TextField(blank=True)
meta.TextField('object_id', blank=True, null=True), class META:
meta.CharField('object_repr', maxlength=200), module_name = 'log'
meta.PositiveSmallIntegerField('action_flag'), verbose_name_plural = 'log entries'
meta.TextField('change_message', blank=True), db_table = 'auth_admin_log'
) ordering = ('-action_time',)
ordering = ('-action_time',) module_constants = {
module_constants = { 'ADDITION': 1,
'ADDITION': 1, 'CHANGE': 2,
'CHANGE': 2, 'DELETION': 3,
'DELETION': 3, }
}
def __repr__(self): def __repr__(self):
return str(self.action_time) return str(self.action_time)

View File

@ -1,12 +1,11 @@
from django.core import meta, validators from django.core import meta, validators
class Site(meta.Model): class Site(meta.Model):
db_table = 'sites' domain = meta.CharField('domain name', maxlength=100)
fields = ( name = meta.CharField('display name', maxlength=50)
meta.CharField('domain', 'domain name', maxlength=100), class META:
meta.CharField('name', 'display name', maxlength=50), db_table = 'sites'
) ordering = ('domain',)
ordering = ('domain',)
def __repr__(self): def __repr__(self):
return self.domain return self.domain
@ -17,25 +16,23 @@ class Site(meta.Model):
return get_object(pk=SITE_ID) return get_object(pk=SITE_ID)
class Package(meta.Model): class Package(meta.Model):
db_table = 'packages' label = meta.CharField(maxlength=20, primary_key=True)
fields = ( name = meta.CharField(maxlength=30, unique=True)
meta.CharField('label', maxlength=20, primary_key=True), class META:
meta.CharField('name', maxlength=30, unique=True), db_table = 'packages'
) ordering = ('name',)
ordering = ('name',)
def __repr__(self): def __repr__(self):
return self.name return self.name
class ContentType(meta.Model): class ContentType(meta.Model):
db_table = 'content_types' name = meta.CharField(maxlength=100)
fields = ( package = meta.ForeignKey(Package, db_column='package')
meta.CharField('name', maxlength=100), python_module_name = meta.CharField(maxlength=50)
meta.ForeignKey(Package, name='package'), class META:
meta.CharField('python_module_name', maxlength=50), db_table = 'content_types'
) ordering = ('package', 'name')
ordering = ('package', 'name') unique_together = (('package', 'python_module_name'),)
unique_together = (('package', 'python_module_name'),)
def __repr__(self): def __repr__(self):
return "%s | %s" % (self.package, self.name) return "%s | %s" % (self.package, self.name)
@ -54,49 +51,45 @@ class ContentType(meta.Model):
return self.get_model_module().get_object(**kwargs) return self.get_model_module().get_object(**kwargs)
class Redirect(meta.Model): class Redirect(meta.Model):
db_table = 'redirects' site = meta.ForeignKey(Site, radio_admin=meta.VERTICAL)
fields = ( old_path = meta.CharField('redirect from', maxlength=200, db_index=True,
meta.ForeignKey(Site, radio_admin=meta.VERTICAL), help_text="This should be an absolute path, excluding the domain name. Example: '/events/search/'.")
meta.CharField('old_path', 'redirect from', maxlength=200, db_index=True, new_path = meta.CharField('redirect to', maxlength=200, blank=True,
help_text="This should be an absolute path, excluding the domain name. Example: '/events/search/'."), help_text="This can be either an absolute path (as above) or a full URL starting with 'http://'.")
meta.CharField('new_path', 'redirect to', maxlength=200, blank=True, class META:
help_text="This can be either an absolute path (as above) or a full URL starting with 'http://'."), db_table = 'redirects'
) unique_together=(('site', 'old_path'),)
unique_together=(('site_id', 'old_path'),) ordering = ('old_path',)
ordering = ('old_path',) admin = meta.Admin(
admin = meta.Admin( list_filter = ('site',),
list_display = ('__repr__',), search_fields = ('old_path', 'new_path'),
list_filter = ('site_id',), )
search_fields = ('old_path', 'new_path'),
)
def __repr__(self): def __repr__(self):
return "%s ---> %s" % (self.old_path, self.new_path) return "%s ---> %s" % (self.old_path, self.new_path)
class FlatFile(meta.Model): class FlatFile(meta.Model):
db_table = 'flatfiles' url = meta.CharField('URL', maxlength=100, validator_list=[validators.isAlphaNumericURL],
verbose_name = 'flat page' help_text="Example: '/about/contact/'. Make sure to have leading and trailing slashes.")
fields = ( title = meta.CharField(maxlength=200)
meta.CharField('url', 'URL', maxlength=100, validator_list=[validators.isAlphaNumericURL], content = meta.TextField()
help_text="Example: '/about/contact/'. Make sure to have leading and trailing slashes."), enable_comments = meta.BooleanField()
meta.CharField('title', maxlength=200), template_name = meta.CharField(maxlength=70, blank=True,
meta.TextField('content', help_text="Full HTML is allowed."), help_text="Example: 'flatfiles/contact_page'. If this isn't provided, the system will use 'flatfiles/default'.")
meta.BooleanField('enable_comments'), registration_required = meta.BooleanField(help_text="If this is checked, only logged-in users will be able to view the page.")
meta.CharField('template_name', maxlength=70, blank=True, sites = meta.ManyToManyField(Site)
help_text="Example: 'flatfiles/contact_page'. If this isn't provided, the system will use 'flatfiles/default'."), class META:
meta.BooleanField('registration_required', db_table = 'flatfiles'
help_text="If this is checked, only logged-in users will be able to view the page."), verbose_name = 'flat page'
meta.ManyToManyField(Site), ordering = ('url',)
) admin = meta.Admin(
ordering = ('url',) fields = (
admin = meta.Admin( (None, {'fields': ('url', 'title', 'content', 'sites')}),
fields = ( ('Advanced options', {'classes': 'collapse', 'fields': ('enable_comments', 'registration_required', 'template_name')}),
(None, {'fields': ('url', 'title', 'content', 'sites')}), ),
('Advanced options', {'classes': 'collapse', 'fields': ('enable_comments', 'registration_required', 'template_name')}), list_filter = ('sites',),
), search_fields = ('url', 'title'),
list_filter = ('sites',), )
search_fields = ('url', 'title'),
)
def __repr__(self): def __repr__(self):
return "%s -- %s" % (self.url, self.title) return "%s -- %s" % (self.url, self.title)
@ -108,18 +101,17 @@ import base64, md5, random, sys
import cPickle as pickle import cPickle as pickle
class Session(meta.Model): class Session(meta.Model):
fields = ( session_key = meta.CharField(maxlength=40, primary_key=True)
meta.CharField('session_key', maxlength=40, primary_key=True), session_data = meta.TextField()
meta.TextField('session_data'), expire_date = meta.DateTimeField()
meta.DateTimeField('expire_date'), class META:
) module_constants = {
module_constants = { 'base64': base64,
'base64': base64, 'md5': md5,
'md5': md5, 'pickle': pickle,
'pickle': pickle, 'random': random,
'random': random, 'sys': sys,
'sys': sys, }
}
def get_decoded(self): def get_decoded(self):
from django.conf.settings import SECRET_KEY from django.conf.settings import SECRET_KEY

View File

@ -11,23 +11,23 @@ class AdminLogNode(template.Node):
def render(self, context): def render(self, context):
if self.user is not None and not self.user.isdigit(): if self.user is not None and not self.user.isdigit():
self.user = context[self.user].id self.user = context[self.user].id
context[self.varname] = log.get_list(user_id__exact=self.user, limit=self.limit, select_related=True) context[self.varname] = log.get_list(user__id__exact=self.user, limit=self.limit, select_related=True)
return '' return ''
class DoGetAdminLog: class DoGetAdminLog:
""" """
Populates a template variable with the admin log for the given criteria. Populates a template variable with the admin log for the given criteria.
Usage:: Usage::
{% get_admin_log [limit] as [varname] for_user [context_var_containing_user_obj] %} {% get_admin_log [limit] as [varname] for_user [context_var_containing_user_obj] %}
Examples:: Examples::
{% get_admin_log 10 as admin_log for_user 23 %} {% get_admin_log 10 as admin_log for_user 23 %}
{% get_admin_log 10 as admin_log for_user user %} {% get_admin_log 10 as admin_log for_user user %}
{% get_admin_log 10 as admin_log %} {% get_admin_log 10 as admin_log %}
Note that ``context_var_containing_user_obj`` can be a hard-coded integer Note that ``context_var_containing_user_obj`` can be a hard-coded integer
(user ID) or the name of a template context variable containing the user (user ID) or the name of a template context variable containing the user
object whose ID you want. object whose ID you want.

View File

@ -387,12 +387,12 @@ def change_list(request, app_label, module_name):
except ObjectDoesNotExist: except ObjectDoesNotExist:
result_repr = EMPTY_CHANGELIST_VALUE result_repr = EMPTY_CHANGELIST_VALUE
else: else:
field_val = getattr(result, f.name) field_val = getattr(result, f.column)
# Foreign-key fields are special: Use the repr of the # Foreign-key fields are special: Use the repr of the
# related object. # related object.
if isinstance(f.rel, meta.ManyToOne): if isinstance(f.rel, meta.ManyToOne):
if field_val is not None: if field_val is not None:
result_repr = getattr(result, 'get_%s' % f.rel.name)() result_repr = getattr(result, 'get_%s' % f.name)()
else: else:
result_repr = EMPTY_CHANGELIST_VALUE result_repr = EMPTY_CHANGELIST_VALUE
# Dates are special: They're formatted in a certain way. # Dates are special: They're formatted in a certain way.
@ -723,10 +723,10 @@ def _get_admin_field(field_list, name_prefix, rel, add, change):
t.append('{{ %soriginal.%s }}' % ((rel and name_prefix or ''), field.name)) t.append('{{ %soriginal.%s }}' % ((rel and name_prefix or ''), field.name))
if change and use_raw_id_admin(field): if change and use_raw_id_admin(field):
if isinstance(field.rel, meta.ManyToOne): if isinstance(field.rel, meta.ManyToOne):
if_bit = '%soriginal.get_%s' % (rel and name_prefix or '', field.rel.name) if_bit = '%soriginal.get_%s' % (rel and name_prefix or '', field.name)
obj_repr = if_bit + '|truncatewords:"14"' obj_repr = if_bit + '|truncatewords:"14"'
elif isinstance(field.rel, meta.ManyToMany): elif isinstance(field.rel, meta.ManyToMany):
if_bit = '%soriginal.get_%s_list' % (rel and name_prefix or '', field.rel.name) if_bit = '%soriginal.get_%s_list' % (rel and name_prefix or '', field.name)
obj_repr = if_bit + '|join:", "|truncatewords:"14"' obj_repr = if_bit + '|join:", "|truncatewords:"14"'
t.append('{%% if %s %%}&nbsp;<strong>{{ %s }}</strong>{%% endif %%}' % (if_bit, obj_repr)) t.append('{%% if %s %%}&nbsp;<strong>{{ %s }}</strong>{%% endif %%}' % (if_bit, obj_repr))
if field.help_text: if field.help_text:
@ -915,21 +915,21 @@ def change_stage(request, app_label, module_name, object_id):
new_data = {} new_data = {}
obj = manipulator.original_object obj = manipulator.original_object
for f in opts.fields: for f in opts.fields:
new_data.update(_get_flattened_data(f, getattr(obj, f.name))) new_data.update(_get_flattened_data(f, getattr(obj, f.column)))
for f in opts.many_to_many: for f in opts.many_to_many:
if f.rel.raw_id_admin: if f.rel.raw_id_admin:
new_data[f.name] = ",".join([str(i.id) for i in getattr(obj, 'get_%s_list' % f.rel.name)()]) new_data[f.name] = ",".join([str(i.id) for i in getattr(obj, 'get_%s_list' % f.rel.singular)()])
elif not f.rel.edit_inline: elif not f.rel.edit_inline:
new_data[f.name] = [i.id for i in getattr(obj, 'get_%s_list' % f.rel.name)()] new_data[f.name] = [i.id for i in getattr(obj, 'get_%s_list' % f.rel.singular)()]
for rel_obj, rel_field in inline_related_objects: for rel_obj, rel_field in inline_related_objects:
var_name = rel_obj.object_name.lower() var_name = rel_obj.object_name.lower()
for i, rel_instance in enumerate(getattr(obj, 'get_%s_list' % opts.get_rel_object_method_name(rel_obj, rel_field))()): for i, rel_instance in enumerate(getattr(obj, 'get_%s_list' % opts.get_rel_object_method_name(rel_obj, rel_field))()):
for f in rel_obj.fields: for f in rel_obj.fields:
if f.editable and f != rel_field: if f.editable and f != rel_field:
for k, v in _get_flattened_data(f, getattr(rel_instance, f.name)).items(): for k, v in _get_flattened_data(f, getattr(rel_instance, f.column)).items():
new_data['%s.%d.%s' % (var_name, i, k)] = v new_data['%s.%d.%s' % (var_name, i, k)] = v
for f in rel_obj.many_to_many: for f in rel_obj.many_to_many:
new_data['%s.%d.%s' % (var_name, i, f.name)] = [j.id for j in getattr(rel_instance, 'get_%s_list' % f.rel.name)()] new_data['%s.%d.%s' % (var_name, i, f.column)] = [j.id for j in getattr(rel_instance, 'get_%s_list' % f.rel.singular)()]
# If the object has ordered objects on its admin page, get the existing # If the object has ordered objects on its admin page, get the existing
# order and flatten it into a comma-separated list of IDs. # order and flatten it into a comma-separated list of IDs.
@ -1095,7 +1095,7 @@ def delete_stage(request, app_label, module_name, object_id):
def history(request, app_label, module_name, object_id): def history(request, app_label, module_name, object_id):
mod, opts = _get_mod_opts(app_label, module_name) mod, opts = _get_mod_opts(app_label, module_name)
action_list = log.get_list(object_id__exact=object_id, content_type_id__exact=opts.get_content_type_id(), action_list = log.get_list(object_id__exact=object_id, content_type__id__exact=opts.get_content_type_id(),
order_by=("action_time",), select_related=True) order_by=("action_time",), select_related=True)
# If no history was found, see whether this object even exists. # If no history was found, see whether this object even exists.
try: try:

View File

@ -43,13 +43,13 @@ def page_not_found(request):
from django.conf.settings import APPEND_SLASH, SITE_ID from django.conf.settings import APPEND_SLASH, SITE_ID
path = request.get_full_path() path = request.get_full_path()
try: try:
r = redirects.get_object(site_id__exact=SITE_ID, old_path__exact=path) r = redirects.get_object(site__id__exact=SITE_ID, old_path__exact=path)
except redirects.RedirectDoesNotExist: except redirects.RedirectDoesNotExist:
r = None r = None
if r is None and APPEND_SLASH: if r is None and APPEND_SLASH:
# Try removing the trailing slash. # Try removing the trailing slash.
try: try:
r = redirects.get_object(site_id__exact=SITE_ID, old_path__exact=path[:path.rfind('/')]+path[path.rfind('/')+1:]) r = redirects.get_object(site__id__exact=SITE_ID, old_path__exact=path[:path.rfind('/')]+path[path.rfind('/')+1:])
except redirects.RedirectDoesNotExist: except redirects.RedirectDoesNotExist:
pass pass
if r is not None: if r is not None:

View File

@ -11,20 +11,16 @@ models, and how to create, retrieve, and update objects.
Throughout this reference, we'll refer to the following Poll application:: Throughout this reference, we'll refer to the following Poll application::
class Poll(meta.Model): class Poll(meta.Model):
fields = ( slug = meta.SlugField(unique_for_month='pub_date')
meta.SlugField('slug', unique_for_month='pub_date'), question = meta.CharField(maxlength=255)
meta.CharField('question', maxlength=255), pub_date = meta.DateTimeField()
meta.DateTimeField('pub_date'), expire_date = meta.DateTimeField()
meta.DateTimeField('expire_date'),
)
class Choice(meta.Model): class Choice(meta.Model):
fields = ( poll = meta.ForeignKey(Poll, edit_inline=meta.TABULAR,
meta.ForeignKey(Poll, edit_inline=meta.TABULAR, num_in_admin=10, min_num_in_admin=5)
num_in_admin=10, min_num_in_admin=5), choice = meta.CharField(maxlength=255, core=True)
meta.CharField('choice', maxlength=255, core=True), votes = meta.IntegerField(editable=False, default=0)
meta.IntegerField('votes', editable=False, default=0),
)
Basic lookup functions Basic lookup functions
====================== ======================
@ -163,23 +159,18 @@ automatically.
One-to-one relations One-to-one relations
-------------------- --------------------
Each object in a one-to-one relationship will have a ``get_relatedobject()`` Each object in a one-to-one relationship will have a ``get_relatedobjectname()``
method. For example:: method. For example::
class Place(meta.Model): class Place(meta.Model):
fields = ( # ...
...
)
class Restaurant(meta.Model): class Restaurant(meta.Model):
... # ...
fields = ( the_place = meta.OneToOneField(places.Place)
meta.OneToOneField(places.Place),
...
)
In the above example, each ``Place`` will have a ``get_restaurant()`` method, In the above example, each ``Place`` will have a ``get_restaurant()`` method,
and each ``Restaurant`` will have a ``get_place()`` method. and each ``Restaurant`` will have a ``get_theplace()`` method.
Many-to-one relations Many-to-one relations
--------------------- ---------------------
@ -236,19 +227,15 @@ Note that ``select_related`` follows foreign keys as far as possible. If you hav
following models:: following models::
class Poll(meta.Model): class Poll(meta.Model):
... # ...
class Choice(meta.Model): class Choice(meta.Model):
fields = ( # ...
meta.ForeignKey(Poll), poll = meta.ForeignKey(Poll)
...
)
class SingleVote(meta.Model): class SingleVote(meta.Model):
fields = ( # ...
meta.ForeignKey(Choice), choice = meta.ForeignKey(Choice)
...
)
then a call to ``singlevotes.get_object(id__exact=4, select_related=True)`` will then a call to ``singlevotes.get_object(id__exact=4, select_related=True)`` will
cache the related choice *and* the related poll:: cache the related choice *and* the related poll::

View File

@ -291,14 +291,9 @@ dictionaries in order of query execution. Each dictionary has the following::
Can I use Django with a pre-existing database? Can I use Django with a pre-existing database?
---------------------------------------------- ----------------------------------------------
Yes. You have two options: Yes. See `Integrating with a legacy database`_.
* Write models that describe your already-existing database layout, and .. _`Integrating with a legacy database`: http://www.djangoproject.com/documentation/legacy_databases/
just point Django at your database.
* Use the alpha ``django-admin.py inspectdb`` function to automatically
create models by introspecting a given database. See `Ticket 90`_.
.. _`Ticket 90`: http://code.djangoproject.com/ticket/90
The admin site The admin site
============== ==============

View File

@ -26,14 +26,14 @@ this document, we'll be working with the following model, a "place" object::
) )
class Place(meta.Model): class Place(meta.Model):
fields = ( name = meta.CharField(maxlength=100),
meta.CharField('name', maxlength=100), address = meta.CharField(maxlength=100, blank=True),
meta.CharField('address', maxlength=100, blank=True), city = meta.CharField(maxlength=50, blank=True),
meta.CharField('city', maxlength=50, blank=True), state = meta.USStateField(),
meta.USStateField('state'), zip_code = meta.CharField(maxlength=5, blank=True),
meta.CharField('zip_code', maxlength=5, blank=True), place_type = meta.IntegerField(choices=PLACE_TYPES)
meta.IntegerField('place_type', choices=PLACE_TYPES) class META:
) admin = meta.Admin()
def __repr__(self): def __repr__(self):
return self.name return self.name

View File

@ -6,15 +6,24 @@ Django's models are the bread and butter of the framework. There's a huge
array of options available to you when defining your data models. This array of options available to you when defining your data models. This
document explains them. document explains them.
Options for models META options
================== ============
A list of all possible options for a model object follows. Although there's a Give your model metadata by using an inner ``"class META"``, like so::
wide array of options, only ``fields`` is required.
class Foo(meta.Model):
bar = meta.CharField(maxlength=30)
# ...
class META:
admin = meta.Admin()
# ...
Here's a list of all possible ``META`` options. No options are required.
``admin`` ``admin``
A ``meta.Admin`` object; see `Admin options`_. If this field isn't given, A ``meta.Admin`` object; see `Admin options`_. If this field is given, the
the object will not have an admin interface. object will have an admin interface. If it isn't given, the object won't
have one.
``db_table`` ``db_table``
The name of the database table to use for the module:: The name of the database table to use for the module::
@ -30,16 +39,6 @@ wide array of options, only ``fields`` is required.
exceptions = ("DisgustingToppingsException", "BurntCrust") exceptions = ("DisgustingToppingsException", "BurntCrust")
``fields``
A list of field objects. See `Field objects`_. For example::
fields = (
meta.CharField('customer_name', maxlength=15),
meta.BooleanField('use_extra_cheese'),
meta.IntegerField('customer_type', choices=CUSTOMER_TYPE_CHOICES),
...
)
``get_latest_by`` ``get_latest_by``
The name of a ``DateField`` or ``DateTimeField``; if given, the module will The name of a ``DateField`` or ``DateTimeField``; if given, the module will
have a ``get_latest()`` function that fetches the "latest" object according have a ``get_latest()`` function that fetches the "latest" object according
@ -69,7 +68,7 @@ wide array of options, only ``fields`` is required.
respect to a parent object. For example, if a ``PizzaToppping`` relates to respect to a parent object. For example, if a ``PizzaToppping`` relates to
a ``Pizza`` object, you might use:: a ``Pizza`` object, you might use::
order_with_respect_to = 'pizza_id' order_with_respect_to = 'pizza'
to allow the toppings to be ordered with respect to the associated pizza. to allow the toppings to be ordered with respect to the associated pizza.
@ -95,7 +94,7 @@ wide array of options, only ``fields`` is required.
``unique_together`` ``unique_together``
Sets of field names that, taken together, must be unique:: Sets of field names that, taken together, must be unique::
unique_together = (("driver_id", "restaurant_id"),) unique_together = (("driver", "restaurant"),)
This is a list of lists of fields that must be unique when considered This is a list of lists of fields that must be unique when considered
together. It's used in the Django admin. together. It's used in the Django admin.
@ -118,18 +117,14 @@ wide array of options, only ``fields`` is required.
Field objects Field objects
============= =============
The list of fields is the most important part of a data model. Each item in The list of fields is the most important part of a data model. Each class
the ``fields`` list is an instance of a ``meta.Field`` subclass and maps to variable in a model, aside from the optional inner ``class META``, should be
a database field. an instance of a ``meta.Field`` subclass.
All field objects -- except for ``ForeignKey`` and ``ManyToManyField`` (see Each field type, except for ``ForeignKey``, ``ManyToManyField`` and
below) -- require the field's machine-readable name as the first positional ``OneToOneField``, takes an optional first positional argument, a
argument. This must be a valid Python identifier -- no spaces, punctuation, human-readable name. If the human-readable name isn't given, Django will use
etc., are allowed. the machine-readable name, converting underscores to spaces.
The second positional argument, a human-readable name, is optional. If the
human-readable name isn't given, Django will use the machine-readable name,
coverting underscores to spaces.
General field options General field options
--------------------- ---------------------
@ -173,6 +168,10 @@ common to all field types. These arguments are:
It is an error to have an inline-editable It is an error to have an inline-editable
relation without at least one core field. relation without at least one core field.
``db_column`` The name of the database column to use for this
field. If this isn't given, Django will use the
field's name.
``db_index`` If ``True``, the SQL generator will create a database ``db_index`` If ``True``, the SQL generator will create a database
index on this field. index on this field.
@ -229,7 +228,7 @@ Field Types
use this directly; a primary key field will automatically be added to your use this directly; a primary key field will automatically be added to your
model if you don't specify otherwise. That automatically-added field is:: model if you don't specify otherwise. That automatically-added field is::
meta.AutoField('id', primary_key=True) id = meta.AutoField(primary_key=True)
``BooleanField`` ``BooleanField``
A true/false field. A true/false field.
@ -370,13 +369,6 @@ Field Types
Not used with ``edit_inline``. Not used with ``edit_inline``.
``rel_name`` The name of the relation. In the above example,
this would default to 'pizza' (so that the
``Toppings`` object would have a ``get_pizza()``
function. If you set ``rel_name`` to "pie", then
the function would be called ``get_pie()`` and the
field name would be ``pie_id``.
``related_name`` The name to use for the relation from the related ``related_name`` The name to use for the relation from the related
object back to this one. For example, when if object back to this one. For example, when if
``Topping`` has this field:: ``Topping`` has this field::
@ -405,11 +397,9 @@ Field Types
... ...
meta.ForeignKey(Category, name="primary_category_id", meta.ForeignKey(Category, name="primary_category_id",
rel_name="primary_category",
related_name="primary_story"), related_name="primary_story"),
meta.ForeignKey(Category, name="secondary_category_id", meta.ForeignKey(Category, name="secondary_category_id",
rel_name="secondary_category",
related_name="secondary_story"), related_name="secondary_story"),
... ...
@ -445,24 +435,18 @@ Field Types
``core.flatfiles`` object:: ``core.flatfiles`` object::
class FlatFile(meta.Model): class FlatFile(meta.Model):
fields = ( # ...
... sites = meta.ManyToManyField(Site)
meta.ManyToManyField(Site),
)
Many-to-many relations are a bit different from other fields. First, they Many-to-many relations are a bit different from other fields. First, they
aren't actually a field per se, because they use a intermediary join table. aren't actually a field per se, because they use a intermediary join table.
Second, they don't take the same options as the rest of the fields. The Second, they don't take the same options as the rest of the fields. The
only arguments taken are: first position argument is required and should be a model class. Other
available arguments, all of which are optional, are:
======================= ============================================================ ======================= ============================================================
Argument Description Argument Description
======================= ============================================================ ======================= ============================================================
``rel_name`` Use this if you have more than one
``ForeignKey`` in the same model that relate
to the same model. Django will use ``rel_name`` in
the generated API.
``related_name`` See the description of ``related_name`` in ``related_name`` See the description of ``related_name`` in
``ForeignKey``, above. ``ForeignKey``, above.
@ -475,14 +459,6 @@ Field Types
``limit_choices_to`` See the description under ``ForeignKey`` above. ``limit_choices_to`` See the description under ``ForeignKey`` above.
``name`` An alphanumeric name for the relationship. If this
isn't provided, Django uses the ``module_name`` of
the related object.
This is only really useful when you have a single
object that relates to the same object more than
once.
``verbose_name`` A human-readable name for the object, singular. If ``verbose_name`` A human-readable name for the object, singular. If
this isn't provided, Django uses the this isn't provided, Django uses the
``verbose_name`` for the related object. ``verbose_name`` for the related object.
@ -534,7 +510,7 @@ Field Types
from which to auto-populate the slug, via JavaScript, in the object's admin from which to auto-populate the slug, via JavaScript, in the object's admin
form:: form::
meta.SlugField("slug", prepopulate_from=("pre_name", "name")), meta.SlugField(prepopulate_from=("pre_name", "name")),
``SmallIntegerField`` ``SmallIntegerField``
Like an ``IntegerField``, but only allows values under a certain Like an ``IntegerField``, but only allows values under a certain
@ -691,9 +667,7 @@ object's behavior. First, any methods you define will be available as methods
of object instances. For example:: of object instances. For example::
class Pizza(meta.Model): class Pizza(meta.Model):
fields = ( # ...
...
)
def is_disgusting(self): def is_disgusting(self):
return "anchovies" in [topping.name for topping in self.get_topping_list()] return "anchovies" in [topping.name for topping in self.get_topping_list()]
@ -742,9 +716,7 @@ that module. Any model method that begins with "_module_" is turned into a
module-level function:: module-level function::
class Pizza(meta.Model): class Pizza(meta.Model):
fields = ( # ...
...
)
def _module_get_pizzas_to_deliver(): def _module_get_pizzas_to_deliver():
return get_list(delivered__exact=False) return get_list(delivered__exact=False)
@ -769,9 +741,7 @@ fields because manipulators automatically call any method that begins with
"validate":: "validate"::
class Pizza(meta.Model): class Pizza(meta.Model):
fields = ( # ...
...
)
def _manipulator_validate_customer_id(self, field_data, all_data): def _manipulator_validate_customer_id(self, field_data, all_data):
from django.core import validators from django.core import validators

View File

@ -21,20 +21,16 @@ offers many rich ways of representing your models -- so far, it's been
solving two years' worth of database-schema problems. Here's a quick example:: solving two years' worth of database-schema problems. Here's a quick example::
class Reporter(meta.Model): class Reporter(meta.Model):
fields = ( full_name = meta.CharField(maxlength=70)
meta.CharField('full_name', maxlength=70),
)
def __repr__(self): def __repr__(self):
return self.full_name return self.full_name
class Article(meta.Model): class Article(meta.Model):
fields = ( pub_date = meta.DateTimeField('pub_date')
meta.DateTimeField('pub_date'), headline = meta.CharField('headline', maxlength=200)
meta.CharField('headline', maxlength=200), article = meta.TextField('article')
meta.TextField('article'), reporter = meta.ForeignKey(Reporter)
meta.ForeignKey(Reporter),
)
def __repr__(self): def __repr__(self):
return self.headline return self.headline
@ -134,17 +130,16 @@ A dynamic admin interface: It's not just scaffolding -- it's the whole house
Once your models are defined, Django can automatically create an administrative Once your models are defined, Django can automatically create an administrative
interface -- a Web site that lets authenticated users add, change and interface -- a Web site that lets authenticated users add, change and
delete objects. It's as easy as adding an extra admin attribute to your model delete objects. It's as easy as adding an extra ``admin`` attribute to your
classes:: model classes::
class Article(meta.Model): class Article(meta.Model):
fields = ( pub_date = meta.DateTimeField('pub_date')
meta.DateTimeField('pub_date'), headline = meta.CharField('headline', maxlength=200)
meta.CharField('headline', maxlength=200), article = meta.TextField('article')
meta.TextField('article'), reporter = meta.ForeignKey(Reporter)
meta.ForeignKey(Reporter), class META:
) admin = meta.Admin()
admin = meta.Admin()
The philosophy here is that your site is edited by a staff, or a client, or The philosophy here is that your site is edited by a staff, or a client, or
maybe just you -- and you don't want to have to deal with creating backend maybe just you -- and you don't want to have to deal with creating backend

View File

@ -167,41 +167,36 @@ These concepts are represented by simple Python classes. Edit the
from django.core import meta from django.core import meta
class Poll(meta.Model): class Poll(meta.Model):
fields = ( question = meta.CharField(maxlength=200)
meta.CharField('question', maxlength=200), pub_date = meta.DateTimeField('date published')
meta.DateTimeField('pub_date', 'date published'),
)
class Choice(meta.Model): class Choice(meta.Model):
fields = ( poll = meta.ForeignKey(Poll)
meta.ForeignKey(Poll), choice = meta.CharField(maxlength=200)
meta.CharField('choice', maxlength=200), votes = meta.IntegerField()
meta.IntegerField('votes'),
)
The code is straightforward. Each model is represented by a class that The code is straightforward. Each model is represented by a class that
subclasses ``django.core.meta.Model``. Each model has a single class variable, subclasses ``django.core.meta.Model``. Each model a number of class variables,
``fields``, which is a tuple of database fields in the model. each of which represents a database field in the model.
Each field is represented by an instance of a ``meta.*Field`` class -- e.g., Each field is represented by an instance of a ``meta.*Field`` class -- e.g.,
``meta.CharField`` for character fields and ``meta.DateTimeField`` for ``meta.CharField`` for character fields and ``meta.DateTimeField`` for
datetimes. This tells Django what type of data each field holds. datetimes. This tells Django what type of data each field holds.
The first argument to each ``Field`` call is the field's name, in The name of each ``meta.*Field`` instance (e.g. ``question`` or ``pub_date`` )
machine-friendly format. You'll use this value in your Python code, and your is the field's name, in machine-friendly format. You'll use this value in your
database will use it as the column name. Python code, and your database will use it as the column name.
The second, optional, argument is the field's human-readable name. That's used You can use an optional first positional argument to a ``Field`` to designate a
in a couple of introspective parts of Django, and it doubles as documentation. human-readable name. That's used in a couple of introspective parts of Django,
If this field isn't provided, Django will use the machine-readable name. In and it doubles as documentation. If this field isn't provided, Django will use
this example, we've only defined a human-readable name for ``Poll.pub_date``. the machine-readable name. In this example, we've only defined a human-readable
For all other fields in this model, the field's machine-readable name will name for ``Poll.pub_date``. For all other fields in this model, the field's
suffice as its human-readable name. machine-readable name will suffice as its human-readable name.
Some ``meta.*Field`` classes have additional required elements. Some ``meta.*Field`` classes have required elements. ``meta.CharField``, for
``meta.CharField``, for example, requires that you give it a ``maxlength``. example, requires that you give it a ``maxlength``. That's used not only in the
That's used not only in the database schema, but in validation, as we'll soon database schema, but in validation, as we'll soon see.
see.
Finally, note a relationship is defined, using ``meta.ForeignKey``. That tells Finally, note a relationship is defined, using ``meta.ForeignKey``. That tells
Django each Choice is related to a single Poll. Django supports all the common Django each Choice is related to a single Poll. Django supports all the common
@ -266,6 +261,9 @@ Note the following:
* Primary keys (IDs) are added automatically. (You can override this, too.) * Primary keys (IDs) are added automatically. (You can override this, too.)
* Django appends ``"_id"`` to the foreign key field name, by convention.
Yes, you can override this, as well.
* 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

View File

@ -89,9 +89,7 @@ objects have an admin interface. Edit the ``myproject/apps/polls/models/polls.py
file and make the following change to add an ``admin`` attribute:: file and make the following change to add an ``admin`` attribute::
class Poll(meta.Model): class Poll(meta.Model):
fields = ( # ...
# ...
)
admin = meta.Admin() admin = meta.Admin()
Now reload the Django admin page to see your changes. Note that you don't have Now reload the Django admin page to see your changes. Note that you don't have
@ -242,15 +240,15 @@ Poll object. Let's make that happen.
Remove the ``admin`` for the Choice model. Then, edit the ``ForeignKey(Poll)`` Remove the ``admin`` for the Choice model. Then, edit the ``ForeignKey(Poll)``
field like so:: field like so::
meta.ForeignKey(Poll, edit_inline=meta.STACKED, num_in_admin=3), poll = meta.ForeignKey(Poll, edit_inline=meta.STACKED, num_in_admin=3)
This tells Django: "Choice objects are edited on the Poll admin page. By This tells Django: "Choice objects are edited on the Poll admin page. By
default, provide enough fields for 3 Choices." default, provide enough fields for 3 Choices."
Then change the other fields in ``Choice`` to give them ``core=True``:: Then change the other fields in ``Choice`` to give them ``core=True``::
meta.CharField('choice', 'choice', maxlength=200, core=True), choice = meta.CharField(maxlength=200, core=True)
meta.IntegerField('votes', 'votes', core=True), votes = meta.IntegerField(core=True)
This tells Django: "When you edit a Choice on the Poll admin page, the 'choice' This tells Django: "When you edit a Choice on the Poll admin page, the 'choice'
and 'votes' fields are required. The presence of at least one of them signifies and 'votes' fields are required. The presence of at least one of them signifies
@ -274,7 +272,7 @@ One small problem, though. It takes a lot of screen space to display all the
fields for entering related Choice objects. For that reason, Django offers an fields for entering related Choice objects. For that reason, Django offers an
alternate way of displaying inline related objects:: alternate way of displaying inline related objects::
meta.ForeignKey(Poll, edit_inline=meta.TABULAR, num_in_admin=3), poll = meta.ForeignKey(Poll, edit_inline=meta.TABULAR, num_in_admin=3)
With that ``edit_inline=meta.TABULAR`` (instead of ``meta.STACKED``), the With that ``edit_inline=meta.TABULAR`` (instead of ``meta.STACKED``), the
related objects are displayed in a more compact, table-based format: related objects are displayed in a more compact, table-based format:

View File

@ -1,3 +1,4 @@
__all__ = ['basic', 'repr', 'custom_methods', 'many_to_one', 'many_to_many', __all__ = ['basic', 'repr', 'custom_methods', 'many_to_one', 'many_to_many',
'ordering', 'lookup', 'get_latest', 'm2m_intermediary', 'one_to_one', 'ordering', 'lookup', 'get_latest', 'm2m_intermediary', 'one_to_one',
'm2o_recursive', 'm2o_recursive2', 'save_delete_hooks', 'custom_pk'] 'm2o_recursive', 'm2o_recursive2', 'save_delete_hooks', 'custom_pk',
'subclassing', 'many_to_one_null']

View File

@ -7,10 +7,8 @@ This is a basic model with only two non-primary-key fields.
from django.core import meta from django.core import meta
class Article(meta.Model): class Article(meta.Model):
fields = ( headline = meta.CharField(maxlength=100, default='Default headline')
meta.CharField('headline', maxlength=100, default='Default headline'), pub_date = meta.DateTimeField()
meta.DateTimeField('pub_date'),
)
API_TESTS = """ API_TESTS = """
# No articles are in the system yet. # No articles are in the system yet.

View File

@ -23,10 +23,8 @@ namespace as custom methods.
from django.core import meta from django.core import meta
class Article(meta.Model): class Article(meta.Model):
fields = ( headline = meta.CharField(maxlength=100)
meta.CharField('headline', maxlength=100), pub_date = meta.DateField()
meta.DateField('pub_date'),
)
def __repr__(self): def __repr__(self):
return self.headline return self.headline

View File

@ -11,12 +11,11 @@ fails.
from django.core import meta from django.core import meta
class Employee(meta.Model): class Employee(meta.Model):
fields = ( employee_code = meta.CharField(maxlength=10, primary_key=True)
meta.CharField('employee_code', maxlength=10, primary_key=True), first_name = meta.CharField(maxlength=20)
meta.CharField('first_name', maxlength=20), last_name = meta.CharField(maxlength=20)
meta.CharField('last_name', maxlength=20), class META:
) ordering = ('last_name', 'first_name')
ordering = ('last_name', 'first_name')
def __repr__(self): def __repr__(self):
return "%s %s" % (self.first_name, self.last_name) return "%s %s" % (self.first_name, self.last_name)

View File

@ -11,11 +11,10 @@ date farthest into the future."
from django.core import meta from django.core import meta
class Article(meta.Model): class Article(meta.Model):
fields = ( headline = meta.CharField(maxlength=100)
meta.CharField('headline', maxlength=100), pub_date = meta.DateTimeField()
meta.DateTimeField('pub_date'), class META:
) get_latest_by = 'pub_date'
get_latest_by = 'pub_date'
def __repr__(self): def __repr__(self):
return self.headline return self.headline

View File

@ -7,11 +7,10 @@ This demonstrates features of the database API.
from django.core import meta from django.core import meta
class Article(meta.Model): class Article(meta.Model):
fields = ( headline = meta.CharField(maxlength=100)
meta.CharField('headline', maxlength=100), pub_date = meta.DateTimeField()
meta.DateTimeField('pub_date'), class META:
) ordering = ('-pub_date', 'headline')
ordering = ('-pub_date', 'headline')
def __repr__(self): def __repr__(self):
return self.headline return self.headline

View File

@ -13,49 +13,43 @@ writer").
from django.core import meta from django.core import meta
class Reporter(meta.Model): class Reporter(meta.Model):
fields = ( first_name = meta.CharField(maxlength=30)
meta.CharField('first_name', maxlength=30), last_name = meta.CharField(maxlength=30)
meta.CharField('last_name', maxlength=30),
)
def __repr__(self): def __repr__(self):
return "%s %s" % (self.first_name, self.last_name) return "%s %s" % (self.first_name, self.last_name)
class Article(meta.Model): class Article(meta.Model):
fields = ( headline = meta.CharField(maxlength=100)
meta.CharField('headline', maxlength=100), pub_date = meta.DateField()
meta.DateField('pub_date'),
)
def __repr__(self): def __repr__(self):
return self.headline return self.headline
class Writer(meta.Model): class Writer(meta.Model):
fields = ( reporter = meta.ForeignKey(Reporter)
meta.ForeignKey(Reporter), article = meta.ForeignKey(Article)
meta.ForeignKey(Article), position = meta.CharField(maxlength=100)
meta.CharField('position', maxlength=100),
)
def __repr__(self): def __repr__(self):
return '%r (%s)' % (self.get_reporter(), self.position) return '%r (%s)' % (self.get_reporter(), self.position)
API_TESTS = """ API_TESTS = """
# Create a few Reporters. # Create a few Reporters.
>>> r1 = reporters.Reporter(id=None, first_name='John', last_name='Smith') >>> r1 = reporters.Reporter(first_name='John', last_name='Smith')
>>> r1.save() >>> r1.save()
>>> r2 = reporters.Reporter(id=None, first_name='Jane', last_name='Doe') >>> r2 = reporters.Reporter(first_name='Jane', last_name='Doe')
>>> r2.save() >>> r2.save()
# Create an Article. # Create an Article.
>>> from datetime import datetime >>> from datetime import datetime
>>> a = articles.Article(id=None, headline='This is a test', pub_date=datetime(2005, 7, 27)) >>> a = articles.Article(headline='This is a test', pub_date=datetime(2005, 7, 27))
>>> a.save() >>> a.save()
# Create a few Writers. # Create a few Writers.
>>> w1 = writers.Writer(id=None, reporter_id=r1.id, article_id=a.id, position='Main writer') >>> w1 = writers.Writer(reporter=r1, article=a, position='Main writer')
>>> w1.save() >>> w1.save()
>>> w2 = writers.Writer(id=None, reporter_id=r2.id, article_id=a.id, position='Contributor') >>> w2 = writers.Writer(reporter=r2, article=a, position='Contributor')
>>> w2.save() >>> w2.save()
# Play around with the API. # Play around with the API.

View File

@ -7,29 +7,25 @@ To define a many-to-one relationship between a model and itself, use
In this example, a ``Category`` is related to itself. That is, each In this example, a ``Category`` is related to itself. That is, each
``Category`` has a parent ``Category``. ``Category`` has a parent ``Category``.
Because of this recursive relationship, we need to tell Django what the Set ``related_name`` to designate what the reverse relationship is called.
relationships should be called. Set ``rel_name`` for this, and set
``related_name`` to designate what the reverse relationship is called.
""" """
from django.core import meta from django.core import meta
class Category(meta.Model): class Category(meta.Model):
module_name = 'categories' name = meta.CharField(maxlength=20)
fields = ( parent = meta.ForeignKey('self', null=True, related_name='child')
meta.CharField('name', maxlength=20), class META:
meta.ForeignKey('self', null=True, module_name = 'categories'
rel_name='parent', related_name='child'),
)
def __repr__(self): def __repr__(self):
return self.name return self.name
API_TESTS = """ API_TESTS = """
# Create a few Category objects. # Create a few Category objects.
>>> r = categories.Category(id=None, name='Root category', parent_id=None) >>> r = categories.Category(id=None, name='Root category', parent=None)
>>> r.save() >>> r.save()
>>> c = categories.Category(id=None, name='Child category', parent_id=r.id) >>> c = categories.Category(id=None, name='Child category', parent=r)
>>> c.save() >>> c.save()
>>> r.get_child_list() >>> r.get_child_list()

View File

@ -4,36 +4,28 @@
In this example, a ``Person`` can have a ``mother`` and ``father`` -- both of In this example, a ``Person`` can have a ``mother`` and ``father`` -- both of
which are other ``Person`` objects. which are other ``Person`` objects.
Because a ``Person`` has multiple relationships to ``Person``, we need to Set ``related_name`` to designate what the reverse relationship is called.
distinguish the relationships. Set ``rel_name`` to tell Django what the
relationship should be called, because ``Person`` has two relationships to the
same model. Also, set ``related_name`` to designate what the reverse
relationship is called.
""" """
from django.core import meta from django.core import meta
class Person(meta.Model): class Person(meta.Model):
fields = ( full_name = meta.CharField(maxlength=20)
meta.CharField('full_name', maxlength=20), mother = meta.ForeignKey('self', null=True, related_name='mothers_child')
meta.ForeignKey('self', null=True, rel_name='mother', father = meta.ForeignKey('self', null=True, related_name='fathers_child')
related_name='mothers_child'),
meta.ForeignKey('self', null=True, rel_name='father',
related_name='fathers_child'),
)
def __repr__(self): def __repr__(self):
return self.full_name return self.full_name
API_TESTS = """ API_TESTS = """
# Create two Person objects -- the mom and dad in our family. # Create two Person objects -- the mom and dad in our family.
>>> dad = persons.Person(id=None, full_name='John Smith Senior', mother_id=None, father_id=None) >>> dad = persons.Person(full_name='John Smith Senior', mother=None, father=None)
>>> dad.save() >>> dad.save()
>>> mom = persons.Person(id=None, full_name='Jane Smith', mother_id=None, father_id=None) >>> mom = persons.Person(full_name='Jane Smith', mother=None, father=None)
>>> mom.save() >>> mom.save()
# Give mom and dad a kid. # Give mom and dad a kid.
>>> kid = persons.Person(id=None, full_name='John Smith Junior', mother_id=mom.id, father_id=dad.id) >>> kid = persons.Person(full_name='John Smith Junior', mother=mom, father=dad)
>>> kid.save() >>> kid.save()
>>> kid.get_mother() >>> kid.get_mother()

View File

@ -10,18 +10,14 @@ and a publication has multiple articles.
from django.core import meta from django.core import meta
class Publication(meta.Model): class Publication(meta.Model):
fields = ( title = meta.CharField(maxlength=30)
meta.CharField('title', maxlength=30),
)
def __repr__(self): def __repr__(self):
return self.title return self.title
class Article(meta.Model): class Article(meta.Model):
fields = ( headline = meta.CharField(maxlength=100)
meta.CharField('headline', maxlength=100), publications = meta.ManyToManyField(Publication)
meta.ManyToManyField(Publication),
)
def __repr__(self): def __repr__(self):
return self.headline return self.headline

View File

@ -1,42 +1,38 @@
""" """
4. Many-to-one relationships 4. Many-to-one relationships
To define a many-to-one relationship, use ForeignKey(). To define a many-to-one relationship, use ``ForeignKey()`` .
""" """
from django.core import meta from django.core import meta
class Reporter(meta.Model): class Reporter(meta.Model):
fields = ( first_name = meta.CharField(maxlength=30)
meta.CharField('first_name', maxlength=30), last_name = meta.CharField(maxlength=30)
meta.CharField('last_name', maxlength=30),
)
def __repr__(self): def __repr__(self):
return "%s %s" % (self.first_name, self.last_name) return "%s %s" % (self.first_name, self.last_name)
class Article(meta.Model): class Article(meta.Model):
fields = ( headline = meta.CharField(maxlength=100)
meta.CharField('headline', maxlength=100), pub_date = meta.DateField()
meta.DateField('pub_date'), reporter = meta.ForeignKey(Reporter)
meta.ForeignKey(Reporter),
)
def __repr__(self): def __repr__(self):
return self.headline return self.headline
API_TESTS = """ API_TESTS = """
# Create a Reporter. # Create a Reporter.
>>> r = reporters.Reporter(id=None, first_name='John', last_name='Smith') >>> r = reporters.Reporter(first_name='John', last_name='Smith')
>>> r.save() >>> r.save()
# Create an Article. # Create an Article.
>>> from datetime import datetime >>> from datetime import datetime
>>> a = articles.Article(id=None, headline='This is a test', pub_date=datetime(2005, 7, 27), reporter_id=r.id) >>> a = articles.Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter=r)
>>> a.save() >>> a.save()
>>> a.reporter_id >>> a.reporter_id
1L 1
>>> a.get_reporter() >>> a.get_reporter()
John Smith John Smith
@ -47,7 +43,7 @@ John Smith
('John', 'Smith') ('John', 'Smith')
# Create an Article via the Reporter object. # Create an Article via the Reporter object.
>>> new_article = r.add_article(headline="John's second story", pub_date=datetime(2005, 7, 28)) >>> new_article = r.add_article(headline="John's second story", pub_date=datetime(2005, 7, 29))
>>> new_article >>> new_article
John's second story John's second story
>>> new_article.reporter_id >>> new_article.reporter_id
@ -61,7 +57,7 @@ John's second story
This is a test This is a test
>>> r.get_article_count() >>> r.get_article_count()
2L 2
# The API automatically follows relationships as far as you need. # The API automatically follows relationships as far as you need.
# Use double underscores to separate relationships. # Use double underscores to separate relationships.
@ -70,4 +66,32 @@ This is a test
>>> articles.get_list(reporter__first_name__exact='John', order_by=['pub_date']) >>> articles.get_list(reporter__first_name__exact='John', order_by=['pub_date'])
[This is a test, John's second story] [This is a test, John's second story]
# Find all Articles for the Reporter whose ID is 1.
>>> articles.get_list(reporter__id__exact=1, order_by=['pub_date'])
[This is a test, John's second story]
# Note you need two underscores between "reporter" and "id" -- not one.
>>> articles.get_list(reporter_id__exact=1)
Traceback (most recent call last):
...
TypeError: got unexpected keyword argument 'reporter_id__exact'
# "pk" shortcut syntax works in a related context, too.
>>> articles.get_list(reporter__pk=1, order_by=['pub_date'])
[This is a test, John's second story]
# You can also instantiate an Article by passing
# the Reporter's ID instead of a Reporter object.
>>> a3 = articles.Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id=r.id)
>>> a3.save()
>>> a3.reporter_id
1
>>> a3.get_reporter()
John Smith
# Similarly, the reporter ID can be a string.
>>> a4 = articles.Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id="1")
>>> a4.save()
>>> a4.get_reporter()
John Smith
""" """

View File

@ -0,0 +1,77 @@
"""
16. Many-to-one relationships that can be null
To define a many-to-one relationship, use ``ForeignKey()`` with ``null=True`` .
"""
from django.core import meta
class Reporter(meta.Model):
name = meta.CharField(maxlength=30)
def __repr__(self):
return self.name
class Article(meta.Model):
headline = meta.CharField(maxlength=100)
reporter = meta.ForeignKey(Reporter, null=True)
def __repr__(self):
return self.headline
API_TESTS = """
# Create a Reporter.
>>> r = reporters.Reporter(name='John Smith')
>>> r.save()
# Create an Article.
>>> a = articles.Article(headline="First", reporter=r)
>>> a.save()
>>> a.reporter_id
1
>>> a.get_reporter()
John Smith
# Article objects have access to their related Reporter objects.
>>> r = a.get_reporter()
# Create an Article via the Reporter object.
>>> a2 = r.add_article(headline="Second")
>>> a2
Second
>>> a2.reporter_id
1
# Reporter objects have access to their related Article objects.
>>> r.get_article_list(order_by=['headline'])
[First, Second]
>>> r.get_article(headline__startswith='Fir')
First
>>> r.get_article_count()
2
# Create an Article with no Reporter by passing "reporter=None".
>>> a3 = articles.Article(headline="Third", reporter=None)
>>> a3.save()
>>> a3.id
3
>>> a3.reporter_id
>>> print a3.reporter_id
None
>>> a3 = articles.get_object(pk=3)
>>> print a3.reporter_id
None
# An article's get_reporter() method throws ReporterDoesNotExist
# if the reporter is set to None.
>>> a3.get_reporter()
Traceback (most recent call last):
...
ReporterDoesNotExist
# To retrieve the articles with no reporters set, use "reporter__isnull=True".
>>> articles.get_list(reporter__isnull=True)
[Third]
"""

View File

@ -9,33 +9,29 @@ In this example, a ``Place`` optionally can be a ``Restaurant``.
from django.core import meta from django.core import meta
class Place(meta.Model): class Place(meta.Model):
fields = ( name = meta.CharField(maxlength=50)
meta.CharField('name', maxlength=50), address = meta.CharField(maxlength=80)
meta.CharField('address', maxlength=80),
)
def __repr__(self): def __repr__(self):
return "%s the place" % self.name return "%s the place" % self.name
class Restaurant(meta.Model): class Restaurant(meta.Model):
fields = ( place = meta.OneToOneField(Place)
meta.OneToOneField(Place), serves_hot_dogs = meta.BooleanField()
meta.BooleanField('serves_hot_dogs'), serves_pizza = meta.BooleanField()
meta.BooleanField('serves_pizza'),
)
def __repr__(self): def __repr__(self):
return "%s the restaurant" % self.get_place().name return "%s the restaurant" % self.get_place().name
API_TESTS = """ API_TESTS = """
# Create a couple of Places. # Create a couple of Places.
>>> p1 = places.Place(id=None, name='Demon Dogs', address='944 W. Fullerton') >>> p1 = places.Place(name='Demon Dogs', address='944 W. Fullerton')
>>> p1.save() >>> p1.save()
>>> p2 = places.Place(id=None, name='Ace Hardware', address='1013 N. Ashland') >>> p2 = places.Place(name='Ace Hardware', address='1013 N. Ashland')
>>> p2.save() >>> p2.save()
# Create a Restaurant. Pass the ID of the "parent" object as this object's ID. # Create a Restaurant. Pass the ID of the "parent" object as this object's ID.
>>> r = restaurants.Restaurant(id=p1.id, serves_hot_dogs=True, serves_pizza=False) >>> r = restaurants.Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
>>> r.save() >>> r.save()
# A Restaurant can access its place. # A Restaurant can access its place.
@ -50,7 +46,7 @@ Demon Dogs the restaurant
>>> p2.get_restaurant() >>> p2.get_restaurant()
Traceback (most recent call last): Traceback (most recent call last):
... ...
RestaurantDoesNotExist: Restaurant does not exist for {'id__exact': ...} RestaurantDoesNotExist: Restaurant does not exist for {'place__id__exact': ...}
# restaurants.get_list() just returns the Restaurants, not the Places. # restaurants.get_list() just returns the Restaurants, not the Places.
>>> restaurants.get_list() >>> restaurants.get_list()
@ -60,4 +56,9 @@ RestaurantDoesNotExist: Restaurant does not exist for {'id__exact': ...}
# Restaurants. # Restaurants.
>>> places.get_list(order_by=['name']) >>> places.get_list(order_by=['name'])
[Ace Hardware the place, Demon Dogs the place] [Ace Hardware the place, Demon Dogs the place]
>>> restaurants.get_object(place__id__exact=1)
Demon Dogs the restaurant
>>> restaurants.get_object(pk=1)
Demon Dogs the restaurant
""" """

View File

@ -16,11 +16,10 @@ undefined -- not random, just undefined.
from django.core import meta from django.core import meta
class Article(meta.Model): class Article(meta.Model):
fields = ( headline = meta.CharField(maxlength=100)
meta.CharField('headline', maxlength=100), pub_date = meta.DateTimeField()
meta.DateTimeField('pub_date'), class META:
) ordering = ('-pub_date', 'headline')
ordering = ('-pub_date', 'headline')
def __repr__(self): def __repr__(self):
return self.headline return self.headline
@ -28,13 +27,13 @@ class Article(meta.Model):
API_TESTS = """ API_TESTS = """
# Create a couple of Articles. # Create a couple of Articles.
>>> from datetime import datetime >>> from datetime import datetime
>>> a1 = articles.Article(id=None, headline='Article 1', pub_date=datetime(2005, 7, 26)) >>> a1 = articles.Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
>>> a1.save() >>> a1.save()
>>> a2 = articles.Article(id=None, headline='Article 2', pub_date=datetime(2005, 7, 27)) >>> a2 = articles.Article(headline='Article 2', pub_date=datetime(2005, 7, 27))
>>> a2.save() >>> a2.save()
>>> a3 = articles.Article(id=None, headline='Article 3', pub_date=datetime(2005, 7, 27)) >>> a3 = articles.Article(headline='Article 3', pub_date=datetime(2005, 7, 27))
>>> a3.save() >>> a3.save()
>>> a4 = articles.Article(id=None, headline='Article 4', pub_date=datetime(2005, 7, 28)) >>> a4 = articles.Article(headline='Article 4', pub_date=datetime(2005, 7, 28))
>>> a4.save() >>> a4.save()
# By default, articles.get_list() orders by pub_date descending, then # By default, articles.get_list() orders by pub_date descending, then

View File

@ -11,10 +11,8 @@ automatically-generated admin.
from django.core import meta from django.core import meta
class Article(meta.Model): class Article(meta.Model):
fields = ( headline = meta.CharField(maxlength=100)
meta.CharField('headline', maxlength=100), pub_date = meta.DateTimeField()
meta.DateTimeField('pub_date'),
)
def __repr__(self): def __repr__(self):
return self.headline return self.headline
@ -22,7 +20,7 @@ class Article(meta.Model):
API_TESTS = """ API_TESTS = """
# Create an Article. # Create an Article.
>>> from datetime import datetime >>> from datetime import datetime
>>> a = articles.Article(id=None, headline='Area man programs in Python', pub_date=datetime(2005, 7, 28)) >>> a = articles.Article(headline='Area man programs in Python', pub_date=datetime(2005, 7, 28))
>>> a.save() >>> a.save()
>>> repr(a) >>> repr(a)

View File

@ -13,10 +13,8 @@ Django provides hooks for executing arbitrary code around ``save()`` and
from django.core import meta from django.core import meta
class Person(meta.Model): class Person(meta.Model):
fields = ( first_name = meta.CharField(maxlength=20)
meta.CharField('first_name', maxlength=20), last_name = meta.CharField(maxlength=20)
meta.CharField('last_name', maxlength=20),
)
def __repr__(self): def __repr__(self):
return "%s %s" % (self.first_name, self.last_name) return "%s %s" % (self.first_name, self.last_name)

View File

@ -0,0 +1,168 @@
"""
15. Subclassing models
You can subclass another model to create a copy of it that behaves slightly
differently.
"""
from django.core import meta
# From the "Bare-bones model" example
from django.models.basic import Article
# From the "Adding __repr__()" example
from django.models.repr import Article as ArticleWithRepr
# From the "Specifying ordering" example
from django.models.ordering import Article as ArticleWithOrdering
# This uses all fields and metadata from Article and
# adds a "section" field.
class ArticleWithSection(Article):
section = meta.CharField(maxlength=30)
class META:
module_name = 'subarticles1'
# This uses all fields and metadata from Article but
# removes the "pub_date" field.
class ArticleWithoutPubDate(Article):
class META:
module_name = 'subarticles2'
remove_fields = ('pub_date',)
# This uses all fields and metadata from Article but
# overrides the "pub_date" field.
class ArticleWithFieldOverride(Article):
pub_date = meta.DateField() # overrides the old field, a DateTimeField
class META:
module_name = 'subarticles3'
# No need to add remove_fields = ('pub_date',)
# This uses all fields and metadata from ArticleWithRepr and
# makes a few additions/changes.
class ArticleWithManyChanges(ArticleWithRepr):
section = meta.CharField(maxlength=30)
is_popular = meta.BooleanField()
pub_date = meta.DateField() # overrides the old field, a DateTimeField
class META:
module_name = 'subarticles4'
# This uses all fields from ArticleWithOrdering but
# changes the ordering parameter.
class ArticleWithChangedMeta(ArticleWithOrdering):
class META:
module_name = 'subarticles5'
ordering = ('headline', 'pub_date')
API_TESTS = """
# No data is in the system yet.
>>> subarticles1.get_list()
[]
>>> subarticles2.get_list()
[]
>>> subarticles3.get_list()
[]
# Create an ArticleWithSection.
>>> from datetime import date, datetime
>>> a1 = subarticles1.ArticleWithSection(headline='First', pub_date=datetime(2005, 8, 22), section='News')
>>> a1.save()
>>> a1
<ArticleWithSection object>
>>> a1.id
1
>>> a1.headline
'First'
>>> a1.pub_date
datetime.datetime(2005, 8, 22, 0, 0)
# Retrieve it again, to prove the fields have been saved.
>>> a1 = subarticles1.get_object(pk=1)
>>> a1.headline
'First'
>>> a1.pub_date
datetime.datetime(2005, 8, 22, 0, 0)
>>> a1.section
'News'
# Create an ArticleWithoutPubDate.
>>> a2 = subarticles2.ArticleWithoutPubDate(headline='Second')
>>> a2.save()
>>> a2
<ArticleWithoutPubDate object>
>>> a2.id
1
>>> a2.pub_date
Traceback (most recent call last):
...
AttributeError: 'ArticleWithoutPubDate' object has no attribute 'pub_date'
# Retrieve it again, to prove the fields have been saved.
>>> a2 = subarticles2.get_object(pk=1)
>>> a2.headline
'Second'
>>> a2.pub_date
Traceback (most recent call last):
...
AttributeError: 'ArticleWithoutPubDate' object has no attribute 'pub_date'
# Create an ArticleWithFieldOverride.
>>> a3 = subarticles3.ArticleWithFieldOverride(headline='Third', pub_date=date(2005, 8, 22))
>>> a3.save()
>>> a3
<ArticleWithFieldOverride object>
>>> a3.id
1
>>> a3.pub_date
datetime.date(2005, 8, 22)
# Retrieve it again, to prove the fields have been saved.
>>> a3 = subarticles3.get_object(pk=1)
>>> a3.headline
'Third'
>>> a3.pub_date
datetime.date(2005, 8, 22)
# Create an ArticleWithManyChanges.
>>> a4 = subarticles4.ArticleWithManyChanges(headline='Fourth', section='Arts',
... is_popular=True, pub_date=date(2005, 8, 22))
>>> a4.save()
# a4 inherits __repr__() from its parent model (ArticleWithRepr).
>>> a4
Fourth
# Retrieve it again, to prove the fields have been saved.
>>> a4 = subarticles4.get_object(pk=1)
>>> a4.headline
'Fourth'
>>> a4.section
'Arts'
>>> a4.is_popular == True
True
>>> a4.pub_date
datetime.date(2005, 8, 22)
# Test get_list().
>>> subarticles1.get_list()
[<ArticleWithSection object>]
>>> subarticles2.get_list()
[<ArticleWithoutPubDate object>]
>>> subarticles3.get_list()
[<ArticleWithFieldOverride object>]
>>> subarticles4.get_list()
[Fourth]
# Create a couple of ArticleWithChangedMeta objects.
>>> a5 = subarticles5.ArticleWithChangedMeta(headline='A', pub_date=datetime(2005, 3, 1))
>>> a5.save()
>>> a6 = subarticles5.ArticleWithChangedMeta(headline='B', pub_date=datetime(2005, 4, 1))
>>> a6.save()
>>> a7 = subarticles5.ArticleWithChangedMeta(headline='C', pub_date=datetime(2005, 5, 1))
>>> a7.save()
# Ordering has been overridden, so objects are ordered
# by headline ASC instead of pub_date DESC.
>>> subarticles5.get_list()
[A, B, C]
"""