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

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 %}

Related

How to add\get data in admin via reversed relation using GenericTabularInline?

I can add Criterias to a place. How can I add Places to a criteria?
Models:
class Criterias(models.Model):
name = ...
class Places(models.Model):
name = ...
class PlacesToCriterias(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
criteria_group = models.ForeignKey(Criterias)
Admin - PLACES part:
class PlaceCriteriasInlineAdmin(GenericTabularInline):
model = PlacesToCriterias
class PlacesAdmin(admin.ModelAdmin):
inlines = [PlaceCriteriasInlineAdmin]
admin.site.register(Places, PlacesAdmin)
In this case, when I open Places admin change page, I can add Criterias items to my 'place'.
Admin - CRITERIAS part:
class CriteriaPlacesInlineAdmin(GenericTabularInline):
model = PlacesToCriterias
class CriteriasAdmin(admin.ModelAdmin):
inlines = [CriteriaPlacesInlineAdmin]
admin.site.register(Criterias, CriteriasAdmin)
In this case, when I open Criterias admin change page, I CAN NOT add Places item to my 'criteria', because instead of possible places I see criterias.
How to get Places items at Criterias admin page?
It was quite easy. GenericTabularInline must be changed to admin.TabularInline
class CriteriaPlacesInlineAdmin(admin.TabularInline):
model = PlacesToCriterias
class CriteriasAdmin(admin.ModelAdmin):
inlines = [CriteriaPlacesInlineAdmin]
admin.site.register(Criterias, CriteriasAdmin)
If anyone needs to get dropdown list with selected object, instead of content_type and object_id fields, the solution is here.

Django filter on generic relationship

I have a model below which points to a generic relationship. This can either be a contact object or a customer object.
class Unsubscribe(models.Model):
"""
Notes:
See: http://www.screamingatmyscreen.com/2012/6/django-and-generic-relations/
"""
content_type = models.ForeignKey(ContentType, help_text="Represents the name of the model")
object_id = models.PositiveIntegerField(help_text="stores the object id")
content_object = generic.GenericForeignKey('content_type', 'object_id')
reason = models.CharField(max_length=60)
request_made = models.DateTimeField(auto_now_add=True,
help_text="Shows when object was created.")
class Meta:
ordering = ['-request_made']
I would like to list out all unsubscribed both unsubscribe customers and contacts only for the user.
queryset = Unsubscribe.objects.filter()
Above gives me all unsubscribe customers and contacts for any users normally I would solve this by doing....
queryset = Unsubscribe.objects.filter(user=request.user)
However, Unsubscribe object does not have a user, but both customers and contacts do.
So how can I filter on the generic relationship?
You could try this
Unsubscribe.objects.filter(content_type__name='user', user=request.user)
For content_type__name='user' specify name of your model class for user or whatever you have associated it with.
Or this also
Unsubscribe.objects.filter(content_type__name='user', object_id=request.user.id)
I assume your model is like:
class Contact(models.Model):
...
user = models.ForeignKey(User)
You can get all the contacts related to the current user using:
contact_ids = [each.id for each in request.user.contact_set.all()]
You can get all the unsubscribed contacts for that user:
unsubscribed_contacts = Unsubscribe.objects.filter(content_type__name='contact', object_id__in=contact_ids)
Remember that a generic foreign key is just two fields, one that is a ForeignKey to a ContentType model, and the other which is the primary key of whichever model you're pointing to. So, to be more generic, you could do something like this:
content_type = ContentType.objects.get_for_model(User)
Unsubscribe.objects.filter(content_type=content_type, object_id=user.id)
for newcommers to this question that have django newer versions , content_type__name is just a property and you can't query with that. instead use content_type__model in your filter methods just like that:
Unsubscribe.objects.filter(content_type__model='user', user=request.user)

Showing inline admin form for GenericForeignKey relationships in django

my models.py
class PagePlaceholder(models.Model):
content_type = models.ForeignKey()
object_id = models.PositiveIntegerField()
display_content = generic.GenericForeignKey('content_type', 'object_id')
class Page(models.Model):
page_name = models.CharField(max_length=1000,default="sample_page")
placeholder = models.ManyToManyField('PagePlaceholder')
the placeholder can refer to any of 4 classes by the generic foreign key relationship.
page has a many2many relationship to placeholder to say that a page can contains a bunch of different models.
and my admin.py for the same.
class PagePlaceholderAdmin(admin.ModelAdmin):
pass
class PageInline(GenericStackedInline):
model = PagePlaceholder
class PageAdmin(admin.ModelAdmin):
inlines = [PageInline,]
now what i want is in the page admin view, to show the form for editing the pageplaceholder's display content object. trying to get that inline gives me an error saying that object is not a class.
is there a decent solution for this with the admin.py or is it necessary for me to create a new form and view to display these model forms properly?

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

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' %}

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?