Django: way to test what class a generic relation content_object is? - django

In my project I have a class, NewsItem. Instances of NewsItem act like a wrapper. They can be associated with either an ArtWork instance, or an Announcement instance.
Here's how the NewsItem model looks:
class NewsItem(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
date = models.DateTimeField(default=datetime.datetime.now,)
class Meta:
ordering = ('-date',)
def __unicode__(self):
return (self.title())
In a template I'm dealing with a NewsItem instance, and would like to output a certain bunch of html it it's 'wrapping' an Artwork instance, and a different bunch of html if it's wrapping an Announcement instance. Could someone explain how I can write a conditional to test for this?
My first naive try looked like this:
{% if news_item.content_object.type=='Artwork' %}do this{% else %}do that{% endif %}

You should use the ForeignKey to content_type, which stores this information.
{% if news_item.content_type.model == 'Artwork' %}

Related

Django models many to many relations

I'm moving my first steps with django and I'm trying to figure out a thing.
Suppose that we have a model.py made like this where NameEffect has a many to many relation
class Name(models.Model):
nameid = models.IntegerField()
name = models.CharField(max_length=255)
class Effect(models.Model):
effectid = models.IntegerField()
effect = models.TextField()
class NameEffect(models.Model):
nameid = models.IntegerField()
effectid = models.IntegerField()
start = models.PositiveIntegerField()
strand = models.PositiveIntegerField()
and I want to create a QuerySet where every entry contains name,effect,start,strand of the researched name. Fact is that the only solution I found was using raw SQL queries but I can't understand how to do it with the django models approach
You haven't defined any relationships here at all. You should identify the fields as ForeignKeys - and also define the implicit many-to-many relationship explicitly (although you don't actually need it for this particular query, but it'll definitely come in useful).
class Name(models.Model):
nameid = models.IntegerField(primary_key=True)
name = models.CharField(max_length=255)
effects = models.ManyToManyField('Effect', through='NameEffect')
class Effect(models.Model):
effectid = models.IntegerField(primary_key=True)
effect = models.TextField()
class NameEffect(models.Model):
name = models.ForeignKey('Name', db_column='nameid')
effect = models.ForeignKey('Effect', db_column='effectid')
start = models.PositiveIntegerField()
strand = models.PositiveIntegerField()
Now you can can query NameEffect directly to get the result you want.
data = NameEffect.objects.values('name__name', 'effect__effect', 'start', 'strand')
Also note, unless you know you really need just these fields, you should avoid using values and just query NameEffect using select_related:
name_effects = NameEffect.objects.select_related('name', 'effect')
and then access the values when you need them, eg in a template:
{% for obj in name_effects %}
{{ obj.name.name }}
{{ obj.effect.effect }}
{{ obj.start }}
{{ obj.strand }}
{% endif %}

How can I traverse a reverse generic relation in a Django template?

I have the following class that I am using to bookmark items:
class BookmarkedItem(models.Model):
is_bookmarked = models.BooleanField(default=False)
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
And I am defining a reverse generic relationship as follows:
class Link(models.Model):
url = models.URLField()
bookmarks = generic.GenericRelation(BookmarkedItem)
In one of my views I generate a queryset of all links and add this to a context:
links = Link.objects.all()
context = {
'links': links
}
return render_to_response('links.html', context)
The problem I am having is how to traverse the generic relationship in my template. For each link I want to be able to check the is_bookmarked attribute and change the add/remove bookmark button according to whether the user already has it bookmarked or not. Is this possible to do in the template? Or do I have to do some additional filtering in the view and pass another queryset?
Since you have defined the 'bookmarks' GenericRelation field, you can just iterate through that:
{% for bookmark in link.bookmarks.all %}

How to filter Many2Many / Generic Relations properly with Q?

I have 3 Models, the TaggedObject has a GenericRelation with the ObjectTagBridge. And the ObjectTagBridge has a ForeignKey to the Tag Model.
class TaggedObject(models.Model):
"""
class that represent a tagged object
"""
tags = generic.GenericRelation('ObjectTagBridge',
blank=True, null=True)
class ObjectTagBridge(models.Model):
"""
Help to connect a generic object to a Tag.
"""
# pylint: disable-msg=W0232,R0903
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
tag = models.ForeignKey('Tag')
class Tag(models.Model):
...
when I am attaching a Tag to an Object, I am creating a new ObjectTagBridge and set its ForeignKey tag to the Tag I want to attach. That is working fine, and I can get all Tags that I attached to my Object very easy. But when I want to get (filter) all Objects that have Tag1 and Tag2 I tried to something like this:
query = Q(tags__tag=Tag1) & Q(tags__tag=Tag2)
object_list = TaggedObjects.filter(query)
but now my object_list is empty, because it is looking for TaggedObjects that have one ObjectTagBridge with 2 tag objects, the first with Tag1 and the second with Tag2.
I my application will be more complex Q queries than this one, so I think I need a solution with this Q object. In fact any combination of binary conjunctions, like: (...) and ( (...) or not(...))
How can I filter this correctly? Every answer is welcome, maybe there is also a different way do achieve this.
thx for your help!!!
If the result you are looking for is a TaggedObject with Tag1 and Tag2, consider querying the TaggedObject instead of querying the ObjectTagBridge. This is what that query might look like:
results = TaggedObject.objects.filter(objecttagbridge__tag = Tag1).filter(objecttagbridge__tag = Tag2)
Essentially we are conducting two filters. Only objects with both Tag1 and Tag2 will pass the filtering criteria and be a part of the result set.
It looks like you are trying to manually implement a Many-to-Many table and then combine that with a generic relation. A better approach might be to let Django handle the M2M for you, and just have it represented on the generic relationship like so:
class TaggedObject(models.Model):
"""
Help to connect a generic object to a Tag.
"""
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
tags = models.ManyToManyField('Tag')
class Tag(models.Model):
...
This should let you do what you were trying to do...
objects = TaggedObject.objects.filter(
Q(tags=Tag1) & Q(tags=Tag2)
)

Help with understanding generic relations in Django (and usage in Admin)

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?
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.
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
http://docs.djangoproject.com/en/1.1/ref/models/querysets/#id4
I'd have given more links but stack wouldn't let me.
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?

Django: check for generic type in template?

I'm using generic types in my Profile model:
user_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
details = generic.GenericForeignKey('user_type', 'object_id')
But now I want to check if a user is a certain type from within my template. I can get the user type with {{ user.get_profile.user_type }} but then what? Or how would I add a method to the model like is_type_xxx so that I could use it in the template?
Maybe I don't fully understand the question, but it seems you can just define a function in your model that returns true if the type is what you need, then you can call that function from the template just as you would if you were accessing a variable.
In the model...
user_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
details = generic.GenericForeignKey('user_type', 'object_id')
def IsTypeX():
return user_type == x
In the template...
{% if user.get_profile.IsTypeX %}
{% endif %}
user_type is a ForeignKey to a ContentType model, so treat it as you would any relation.
Although Ignacio is basically correct, I find that there can be a really tendency to get a lot of unintended DB hits if you're not careful. Since the number of ContentTypes tends to be small and relatively unchanging, I cache a dict of name/id pairs to avoid it. You can use a signal to update your dict on the off chance a new ContentType is created.
You can then auto-create the necessary is_type_xxx() functions/methods as needed. It's a little klunky, but the code isn't very complicated.