Opened 19 years ago

Closed 18 years ago

Last modified 17 years ago

#529 closed enhancement (fixed)

Add support for GenericForeignKey

Reported by: Adrian Holovaty Owned by: Adrian Holovaty
Component: Contrib apps Version:
Severity: critical Keywords: rthml tab space editor js
Cc: hi-world, cup Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

sopel had the idea of a GenericForeignKey, which would abstract the concept of "content_type_id" and "object_id". Basically, it'd be a way to relate an object to "one of several types of objects."

For example, we currently have this in the Comment model:

class Comment(meta.Model):
    # ...
    content_type = meta.ForeignKey(core.ContentType)
    object_id = meta.IntegerField()

That could be replaced with this:

class Comment(meta.Model):
    # ...
    content_object = meta.GenericForeignKey()

With that, Comment objects would get an automatic get_content_object() method, which would return whatever object was related, regardless of its type.

This is a messy problem, so we'd have to figure out a couple of loose ends:

  • Does every other object in the system get a get_comment_list method?
  • Do we enforce referential integrity, so that, for example, all the appropriate comments would be deleted if a story was deleted?

Change History (15)

comment:1 by sopel, 19 years ago

Regarding: Does every other object in the system get a get_comment_list method?

It should be possible to specify what models you can relate to from a given GenericFK, so you'd say: object = GenericForeignKey(Laptop, Server, Desktop). This way the above problem goes away.

This also solves the problem of our object instances being cleanly and quickly removed when a Laptop is removed. Without specifying the models that we relate to, every model on deletion would have to do a DELETE from each of the tables of models with GenericForeignKey's. And if we knew what models a GenericForeignKey can relate to - we would only issue one DELETE - to the right table.

comment:2 by robert@…, 19 years ago

This seems kind of evil from a relational point of view...

What is the disadvantage of having a Comment superclass ( that could be table-less),
and just subclassing for each thing you want to have comments on?

class Comment(meta.AbstractModel):

... some fields...

class LaptopComment(Comment):

content_object = meta.ForeignKey(Laptop)

class ServerComment(Comment):

content_object = meta.ForeignKey(Server)

To make this seamless, superclasses should aggregate list methods of their subclasses.
Also, I'm not sure what the right thing to do is if the target of a ForeignKey has subclasses...
I don't think any solution is great there.

comment:3 by Boffbowsh, 18 years ago

Cc: paul.bowsher@… added

comment:5 by Boffbowsh, 18 years ago

After a bit of discussion on IRC with rjwittams, I have a solution. Basically, I need this functionality by the end of the week, and am going to have to shortcut it until a better solution is discussed.

Essentially, i'd like to have this model:

from django.core import meta

class Comment (meta.Model):
	body = meta.TextField()
	
	self.get_subject = self.get_commentobject().get_comment_subject
	
class BlogEntry (meta.Model):
	body = meta.TextField()
	who = meta.CharField(max_length=32)
	
class DocEntry (meta.Model):
	body = meta.TextField()
	function = meta.CharField(max_length=32)

class FAQEntry (meta.Model):
	question = meta.TextField()
	answer = meta.TextField()
	
class CommentObject (meta.Model):
	comment = meta.OneToOneField(Comment)
	blogentry = meta.OneToOneField(BlogEntry, null=True)
	docentry = meta.OneToOneField(DocEntry, null=True)
	faqentry = meta.OneToOneField(FAQEntry, null=True)
	
	def get_comment_subject(self):
		for field in (blogentry, docentry, faqentry):
			if field:
				return field
		return None

Shortcutted to:
{{{from django.core import meta

class Comment (meta.Model):

body = meta.TextField()
subject = meta.GenericForeignKey([BlogEntry, DocEntry, FAQEntry])


class BlogEntry (meta.Model):

body = meta.TextField()
who = meta.CharField(max_length=32)


class DocEntry (meta.Model):

body = meta.TextField()
function = meta.CharField(max_length=32)

class FAQEntry (meta.Model):

question = meta.TextField()
answer = meta.TextField()

}}}

comment.get_subject() should return either a BlogEntry, FAQEntry or DocEntry. Not entirely sure how this should look in the admin interface though.

comment:6 by Boffbowsh, 18 years ago

Preview function is my friend

from django.core import meta

class Comment (meta.Model):
	body = meta.TextField()
	subject = meta.GenericForeignKey([BlogEntry, DocEntry, FAQEntry])
	
class BlogEntry (meta.Model):
	body = meta.TextField()
	who = meta.CharField(max_length=32)
	
class DocEntry (meta.Model):
	body = meta.TextField()
	function = meta.CharField(max_length=32)

class FAQEntry (meta.Model):
	question = meta.TextField()
	answer = meta.TextField()

comment:7 by Boffbowsh, 18 years ago

Ignore my last two comments, they're wrong. rjwittams' workaround is:

from django.core import meta

class Comment (meta.Model):
	body = meta.TextField()

	def get_parent(self):
	    if not self.getters:
		getters = [self.get_comment_reply, self.get_blogcomment, self.get_faqcomment, self.get_doccomment]
	    for getter in self.getters:
		ext = getter()
		if ext:
		   return ext.parent      

class BlogEntry (meta.Model):
	body = meta.TextField()
	who = meta.CharField(max_length=32)
	
class DocEntry (meta.Model):
	body = meta.TextField()
	function = meta.CharField(max_length=32)

class FAQEntry (meta.Model):
	question = meta.TextField()
	answer = meta.TextField()
	
class BlogComment (meta.Model):
	comment = meta.OneToOneField(Comment)
	parent = meta.ForeignKey(BlogEntry)

class CommentReply (meta.Model):
	comment = meta.OneToOneField(Comment)
	parent = meta.ForeignKey(Comment)

class DocComment(meta.Model):
	comment = meta.OneToOneField(Comment)
	parent =  meta.ForeignKey(DocEntry)
	
class FaqComment(meta.Model):
	comment = meta.OneToOneField(Comment)
	parent = meta.ForeignKey(FAQEntry)

comment:8 by Here, 18 years ago

Type: defect

comment:9 by Jacob, 18 years ago

Resolution: fixed
Status: newclosed

(In [3134]) Added generic foreign key support to Django. Much thanks to Ian Holsman and
Luke Plant -- most of this code is theirs. Documentation is to follow; for now
see the example/unit test. Fixes #529.

comment:10 by anonymous, 18 years ago

Type: defect

comment:11 by anonymous, 18 years ago

Cc: paul.bowsher@… removed

Remove CC to prevent spam - sorry

comment:12 by anonymous, 18 years ago

Component: MetasystemContrib apps
milestone: Version 1.0
priority: lowhigh
Severity: normalcritical
Type: defectenhancement

comment:13 by hi-world cup, 18 years ago

Cc: hi-world cup added
Keywords: rthml tab space editor js added
Summary: Add support for GenericForeignKeyhi-world cup

comment:14 by Adrian Holovaty, 18 years ago

Summary: hi-world cupAdd support for GenericForeignKey

comment:15 by (none), 17 years ago

milestone: Version 1.0

Milestone Version 1.0 deleted

Note: See TracTickets for help on using tickets.
Back to Top