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

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?

Related

Django template not displaying data from database

I am running into an issue where my database is information is displaying on one template, but I want certain parts to display on another page for a blog.
When I click into physics-blog it will display my images and post title. For this, I have looped through the database. Works fine and perfectly.
But when I click into one of them and want to show {{ physicsBlog.body }} it doesn't show anything. Which I can't wrap my head around because that works just fine in the other ListView template, but not in the DetailView template.
Here is my code.
models.py
class physicsBlog(models.Model):
title = models.CharField(max_length=250)
blog_image = models.ImageField(null=True, blank=True)
description = models.CharField(max_length=200)
author = models.ForeignKey(User, on_delete=models.CASCADE)
body = RichTextField(blank=True, null=True)
date_created = models.DateField(auto_now_add=True)
def __str__(self):
return self.title + ' | ' + str(self.author)
views.py
class physicsBlogListView(ListView):
model = physicsBlog
template_name = 'physics.html'
ordering = ['-id']
class physicsBlogDetailView(DetailView):
model = physicsBlog
template_name = 'physics-blog-details.html'
urls.py
urlpatterns = [
path('', views.home, name="home"),
path('physics-blog', physicsBlogListView.as_view(), name="physics-blog"),
path('physics-blog/<int:pk>', physicsBlogDetailView.as_view(), name="physics-blog-details"),
path('crypto-blog', cryptoBlogListView.as_view(), name="crypto-blog"),
path('crypto-blog/<int:pk>', cryptoBlogDetailView.as_view(), name="crypto-blog-details"),
]
I'm not super familiar using these generic displays that Django provides, but from the django docs it says
While this view is executing, self.object will contain the object that
the view is operating upon.
So maybe try {{ object.body }} in your template?
You can use standard way of rendering data in standard single-object view (mostly DetailView):
{{ object.body }}
But if you want to see it better, just add context_object_name variable:
class physicsBlogDetailView(DetailView):
context_object_name = "physics_blog"
...
After such change in template:
{{ physics_blog.body }}
Without context_object_name it's object or object_list. I'm kinda surprised that you did ok in ListView and had problem in DetailView, because it's the same thing.

Can I have data from different fields of a model returned as separate HTML elements? (Django 2.1)

I am compiling a database of articles and have my model set up like this:
class articles(models.Model):
ArticleID = models.IntegerField(primary_key=True)
Title = models.CharField(max_length=500)
Author = models.CharField(max_length=200, null=True)
Journal = models.CharField(max_length=500, null=True)
Date = models.IntegerField(null=True)
Issue = models.IntegerField(null=True)
Link = models.URLField(max_length=800, null=True)
Content = models.TextField()
class Meta:
db_table = 'TEST'
def __str__(self):
return f'{self.Title}, {self.Author}, {self.Journal},{self.Date}, {self.Issue}, {self.Content}'
def get_absolute_url(self):
return reverse('article-detail', args=[str(self.ArticleID)])
The idea is pretty simple. Each meta data type (i.e. title, author) is it's own field, and the actual content of the article is in the field Content.
My view for this model:
def article_detail(request, ArticleID):
ArticleID = get_object_or_404(articles, ArticleID=ArticleID)
context = {'ArticleID': ArticleID}
return render(request, 'article_detail.html', context)
The HTML template for the view:
{% extends 'base.html' %}
{% block content %}
<div class="container">
{{ ArticleID }}
</div>
{% endblock %}
The data displayed in on the HTML page is one big block of text in one single HTML element. How can I make it so that I can use CSS to target each individual field from the model? Must I make separate models for each field (and bound them with foreign keys)?
No of course not. You can access fields with normal dot notation: ArticleID.Title, ArticleID.Author, etc.
(But you shouldn't call your context variable ArticleID; it's the whole article, not the ID. Also, Python style is to use lower_case_with_underscore for variables and attribute names.)
There is already a primary key in for every model which is called id, you don't have to explicitly declare that.
Secondly you are getting an article object with get_object_or_404 so if you use . (dot) notation you will get your desired value in your template.
Something like-
<h2>{{article.Title}}</h2>
<p>{{article.Content}}</p>
though you have to send article names instead of ArticleID in context variable.
In addition to Mr. Daniel Roseman's comment you should use class name Article instead of articles which is not pythonic.

Django Templates: Accessing FK methods

Good Afternoon,
I'd like to be able to display a returned value from a method that is in a foreign table (the template obj shares a one-many relationship):
models.py
class teacher(stuff):
name = models.charfield()
website = models.URLField()
slug = models.SlugField(max_length=255, db_index=True, unique=True)
def get_page_url(self):
return reverse('teacher_page', args=[self.slug])
class homework(stuff):
assignment = models.ForeignKey(teacher)
due_date = models.DateField()
other assignment stuff..
I pass the assignment object into my template, and can access it's attributes like so:
{{homework.due_date}}
but let us say I want to display the page of the teacher who has assigned the homework. I thought I should be able to access it like so:
<a href='{{homework.teacher_set.get_page_url}}'>teacher page</a>
But this just results in an attribute error.
How can I get the url?
NOTE: This example was written on the fly for the purpose of conveying the question only. No syntax trolls!
Since it is a forward relationship, you would just do
{{homework.assignment.get_page_url}}
Further, if you are looking for a reverse foreign key relationship, you would do
{{teacher.name}}
{% for homework in teacher.homework_set.all %}
{{homework.due_date}}
{% endfor %}
because teacher.homework_set returns a Manager

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

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