views:

90

answers:

3

I'm building a CMS for my company's website (I've looked at the existing Django solutions and want something that's much slimmer/simpler, and that handles our situation specifically.. Plus, I'd like to learn this stuff better). I'm having trouble wrapping my head around generic relations.

I have a Page model, a SoftwareModule model, and some other models that define content on our website, each with their get_absolute_url() defined. I'd like for my users to be able to assign any Page instance a list of objects, of any type, including other page instances. This list will become that Page instance's sub-menu.

I've tried the following:

class Page(models.Model):
    body = models.TextField()
    links = generic.GenericRelation("LinkedItem")

    @models.permalink
    def get_absolute_url(self):
        # returns the right URL

class LinkedItem(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')

    title = models.CharField(max_length=100)

    def __unicode__(self):
        return self.title

class SoftwareModule(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField()

    def __unicode__(self):
        return self.name

    @models.permalink
    def get_absolute_url(self):
        # returns the right URL

This gets me a generic relation with an API to do page_instance.links.all(). We're on our way. What I'm not sure how to pull off, is on the page instance's change form, how to create the relationship between that page, and any other extant object in the database. My desired end result: to render the following in a template:

<ul>
{% for link in page.links.all %}
    <li><a href='{{ link.content_object.get_absolute_url() }}'>{{ link.title }}</a></li>
{% endfor%}
</ul>

Obviously, there's something I'm unaware of or mis-understanding, but I feel like I'm, treading into that area where I don't know what I don't know. What am I missing?

A: 

I haven't seen templates access managers directly before as in the use of page.links.all

From my understanding you need to pull back the links as a list in a view and pass that as a variable to the template. Also, you need to resolve any foreign keys ahead of time which you can do by using select_related.

ie.

def some_view(request,*args,**kwargs):

  ...
  page_links = page_instace.links.select_related().all()
  ...

  return render_to_response(
                'the_template.html',
                #extra_context to pass to the template as var_name:value
                {
                    "page_links":page_links,
                },
                # needed if you need access to session variables like user info
                context_instance=RequestContext(request)
            )

then in the template...

<ul>
{% for link in page_links %}
    <li><a href='{{ link.content_object.get_absolute_url() }}'>{{ link.title }}</a></li>
{% endfor%}
</ul>

see

I'd have given more links but stack wouldn't let me.

grant
A: 

Why are you trying to access link.content_object inside the page.link.all() list? Inside this list, link.content_object will always be the same as page.

I don't think I understand what you're trying to do here, but right now that code should generate a list of links all to the current page with the link.title text.

Can you explain what you are trying to do with LinkedItem?

Casey Stark
Casey I think what he's trying to do is to link some OTHER generic item to a Page model. The content_type/obj_id are for the other item.
T. Stone
I think saturdayplace needs to indicate what he is using content_object for and why he wants a reverse relation in Page. My understanding is that he would like to use LinkedItem objects as children of objects beside Page objects, in which case, you shouldn't have an explicit page field in LinkedItem.
Casey Stark
Actually, T. Stone divined my intent. I *did* only want LinkedItem objects as children of Page objects. I just wanted LinkedItems to be many different kinds of other objects.
saturdayplace
Then why do you need a generic relation?
Casey Stark
@Casey - I'd like for any model that has get_absolute_url to be a LinkedItem. So I can't just do links = models.MTMField("OtherModel") because that limits the kinds of things I can stick in a pages links field to one model.
saturdayplace
OH. I didn't understand what LinkedItem was doing. Any reason you don't just subclass LinkedItem?
Casey Stark
+1  A: 

How are the LinkedItem associated with a Page? A GenericRelation is used for a reverse relationship, but as it stands now there isn't any relationship so it has nothing to match to. I think this is what you're looking for in your model design:

class Page(models.Model):
    body = models.TextField()
    # Moved generic relation to below

class LinkedItem(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')

    # LinkedItems now relate to a Page model, and we're establishing the relationship
    # by specifying 'links' to keep the syntax you're looking for
    page = models.ForeignKey(Page, related_name='links')

    title = models.CharField(max_length=100)

On a side note, this model setup allows one LinkedItem to relate to a Page. If you wanted to re-use linkeditems, you could make it a M2M:

class Page(models.Model):
    body = models.TextField()
    links = models.ManyToManyField(LinkedItem)

class LinkedItem(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')

    title = models.CharField(max_length=100)

In both of these instances, page.links.all() will be all of the linked items.

Also, parenthesis aren't used in the template syntax.

T. Stone
That's it! I'd been staring at this for too long at the end of the day. Thanks for puzzling out my intent when I wasn't exactly clear.
saturdayplace