Django recursive relationship nesting in template question - django
First off, I want to say thank you to all of you here at stack-overflow. I have researched and been
able to find many of the answers to the questions I had on recursive relationships in Django; all
the way from Model creation, to views, to now the template. I am new to Python, therefore,
new to Django. So I thank you for the detailed examples in response to questions others have asked.
tl;dr
I needed to have a nested (ordered) list that has a foreign key relationship with another model. I tried using a recursive foreign key to "self" and was not able to accomplish the task with my research. Thanks to #somecallitblues (can someone tell me how to give him credit?)for pointing out Django-mptt, I was able to solve this problem.
(I don't know if or how to mark this question as answered and/or if I should change the title for others to be able to search if they have the same problem. I could use some help on formatting my question(s)/or the correct way to ask. Any pointers would be helpful.)
Models
from mptt.models import TreeForeignKey, MPTTModel
class Topic(models.Model):
wherein = models.CharField()
partof = models.CharField()
title = models.CharField()
slug = models.SlugField()
class TextNode(MPTTModel):
topic = models.ForeignKey(Topic)
parent = TreeForeignKey(
"self",
on_delete=models.CASCADE,
related_name="children",
related_query_name="child",
null=True,
blank=True,
)
body = models.TextField()
View
def topic_node_list(request, slug):
topics = Topic.objects.filter(slug__exact=slug)
textnodes = TextNode.objects.filter(topic__slug__exact=slug)
return render(request, "node_test.html", {"topics": topics, "textnodes": textnodes})
Template
{% load mptt_tags %}
<ul>
{% for topic in topics %}
<h2>{{ topic.title }}</h2>
<ul>
{% recursetree textnodes %}
<li>
{{ node.body}}
{% if not node.is_leaf_node %}
<ul class="children">
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
</ul>
{% endfor %}
</ul>
urls.py
urlpatterns = [
path("nodes/<slug:slug>/", views.topic_node_list, name="nodes"),
path("", views.home, name="home"),
path('admin/', admin.site.urls),
]
Everything works great! So thank you again #somecallitblues!
BUT! I would like to change to to an ordered list, and depending on the nested level, change the type. I would like the first ordered list to be 1.,2.,3 the next to be a., b., c., then i.,ii. iii. etc. Could the be done via JavaScript or jQuery? I do not even know where to look or what to search for in my search engine.
This is my first time doing a project in Python. I just started learning in December and I was tied of doing simple blogs and needed a challenge, and boy oh boy did I learn a lot! I am recovering from a major illness and have been out of commission for almost a decade, so getting back into the swing of things has been challenging. Python has helped me immensely for regaining muscle memory.
end tl;dr
I am trying to create a "report" (for lack of a better term) app. It has a table of contents, introductory,
however many sections you would like, bibliography, footnotes, index, glossary etc. I also have an app for
for questions that can be asked along with the ability to answer said questions, this is where I am having an
issue and the basis for my question. My code for Models, View and Template will be added below.
(if I can figure out how to post code).
I have a Subject model and a Body model. The Subject model is what the topic of the question is about.
The Body model is where the text of the questions are stored. Each question may or may not have a
sub-question. The Body model has a Foreign Key to the Subject model. The Body model has a recursive
Foreign Key to "self". This relationship is working fine, though I do have a "how do I" question
that I will list below. **
For the View I have a Generic ListView of the Subject model and added the extra context for the Body model.
The Template is the basis of my problem. I am able to nest ordered list of the "subject" and
the "body" of of the question being asked.
just fine. It's where I post the children of the questions (or sub-question).
The template will list the parent question in an ordered list along with it's children. Then the children
start listing as a parent question, so in essence they are posted twice. Once correctly as sub-question,
and then incorrectly as parent question (though I would like to know how to list a sub-question as the
parent to a sub-question (a grandchild relationship?).)
I have thought of doing both a custom model manager or class method(s) to sort out the parents from
the children, but how would I go about doing that? As I said, I am new to python and Django, so I haven't
a clue.
I am using:
Django version: 2.1.8
Django-pyodbc-azure 2.1
python 3.7.2
Here is my code:
Models
class Subject(models.Model):
...
wherein = models.CharField(
max_length=3,
choices=WHERE,
default=SECTION,
help_text="Where in the report",
)
partof = models.CharField(
max_length=5,
choices=PART_OF,
default=QUESTION,
help_text="Is this a question or answer?")
title = models.CharField(
max_length=80,
help_text="What is the subject of the question",
)
slug = models.SlugField(
max_length=80,
help_text="This is what the url will be"
)
class Body(models.Model):
subject = models.ForeignKey(
Subject,
on_delete=models.CASCADE,
verbose_name="subject of",
help_text="What subject does this text relate to?"
)
parent = models.ForeignKey(
"self",
on_delete=models.CASCADE,
verbose_name="subsection of",
related_name="subsections",
related_query_name="subsection",
null=True,
blank=True,
)
text = models.TextField(
max_length=1500,
help_text="Text of the paragraph",)
View
class QuestionList(ListView):
queryset = Subject.objects.filter(id=2)
template_name = "questions/questions_list.html"
context_object_name = "subjects"
def get_context_data(self, *args, object_list=None, **kwargs):
context = super(QuestionDetail, self).get_context_data(**kwargs)
context["bodies"] = Body.objects.filter(subject__partof__exact="QUES")
return context
Template
<ol type="1">
{% for subject in subjects %}
<li><b>{{ subject.title }}</b></li>
<ol type="a">
{% for body in bodies %} # I believe this is where I need to filter for just parents
<li>{{ body.text }}</li>
<ol type="i">
{% for sub in body.subsections.all %}
<li>{{ sub.text }}</li>
{% endfor %}
</ol>
{% endfor %}
</ol>
</ol>
{% endfor %}
I would like to have an option to nest further (have children of the child of the parent).
Should I add extra context for parents:
Body.objects.filter(parent_id__isnull=True)?
if so, how, in the templates, would that work with the relationship w/the children? (nesting the list).
** a side question: How would I make "child" a parent to their own "child"?
and how would those be nested in the template?
I hope I have laid out my issue properly, I tried to post as much as I could and I am hoping it's an easy
way to solve. If there is anything that I have missed, please feel free to ask!
Thank you!
UPDATE:
Here is what the template to render as html
# The first ol
<li>1. This would be the topic a question</li>
<li>a. This would be the parent question.</li>
<li>i. This would be the child of the parent question a.</li>
<li> ii. This would also be a child of the parent question a.</li>
<li>b. This would be the second parent question</li>
<li>c. This would be the third parent question</li>
<li>i. This would be the child of the parent question c.</li>
<li> ii. This would also be a child of the parent question c.</li>
<li>d. … and so on</li>
<li>2. This would be the next topic of a question</li>
UPDATE 2:
I realized I didn't update how things are rendering now. Here it is:
<li>1. This would be the topic a question</li>
<li>a. This would be the parent question.</li>
<li>i. This would be the child of the parent question a.</li>
<li> ii. This would also be a child of the parent question a.</li>
<li>b. This would be the second parent question</li>
<li>c. This would be the third parent question</li>
<li>i. This would be the child of the parent question c.</li>
<li> ii. This would also be a child of the parent question c.</li>
<li>d. … and so on</li>
<li>e. This would be the child of the parent question a.</li>
<li>f. This would also be a child of the parent question a.</li>
<li>g. This would be the child of the parent question c.</li>
<li>h. This would also be a child of the parent question c.</li>
<li>i. … and so on</li>
<li>2. This would be the next topic of a question</li>
I am current reading Django docs about adding a custom model manager. A bit confused, but I will persevere on trying new ways of doing things. Like I said, I am a bit lost at this point in time and could really use some help.
UPDATE 3
I took the advice of #somecallitblues and took the time to go through both Django-treebead and Django-mptt. With Treebeard I couldn't figure out how to work with templates. With mppt I was able to build the models, but unable to connect the two models together for displaying in the template, and the view I had to hard-code a topic_id. I feel like I am missing something. Can anybody point me to where my problems are? I am reading all the documentations, but not understanding where and how to apply the methods. I am very new to web development along w/being a newbie to python.
I will post what I have right now, and if someone can give me some pointers on what I should be looking into, I would be grateful.
For the models, Subject is still the same, except it's named Topic, so I won't repost that:
updated body model:
class BodyNode(MPTTModel):
topic = models.ForeignKey(
Topic,
on_delete=models.CASCADE,
verbose_name="Of topic",
help_text="What topic is being discussed?",
)
text = models.TextField(
max_length=750,
help_text="Content of the paragraph",
)
parent = TreeForeignKey(
"self",
on_delete=models.CASCADE,
related_name="children",
blank=True,
null=True,
)
View:
def topic_list(request):
bodynodes = BodyNode.objects.filter(topic_id=2)
return render(request, "mpttapp/topic_list.html", {"bodynodes": bodynodes})
Template:
<ol class="root" type="a">
{% recursetree bodynodes %}
<li>
{{ node.text }}
{% if not node.is_leaf_node %}
<ol type="i" class="children">
{{ children }}
</ol>
{% endif %}
</li>
{% endrecursetree %}
</ol>
Right now, the list displays as it should, but only for topid_id=2.I need that to be dynamic, along with being able to add the slug field for the url,along with being able to use the title field from the Topic model, so I am half way there.
I do want to thank #somecallitblues for helping me, I feel that using mptt is the way to go as I'll be able to do deeper nesting if need be. I just feel like I am a bit over my head, but I am really determined to figure this out. I just need some pointers.
UPDATE 4
I think I have figured out the relationship problem. I am going to make the Topic Model into an MPTTModel w/a TreeForeignKey from the BodyNode. I think the relationship to the Topic model is the nesting issue I am having. Since the app itself is "sort of" a tree/directory object to begin with, that could be the reasons for my struggles. I'll update again tomorrow if this works, I would really like to solve this and have this topic be helpful for anyone else who comes along w/the same issue.
I do spend A LOT of time reading the documentation on Django-project site, yet I am still a bit confused on building the templates/views. If anyone knows a good tutorial site or a good book that does more than build a simple blog, I would be grateful for the recommendation.
Related
Loop over related model's children in Django template
I have a model for a company. Then I have a base model for company posts. It contains common posts attributes. An attribute is the company that publishes the posts. It refers to the Company model with a ForeignKey. Finally I have a child model (based on the CompanyPost base model) for posts of type A: class Company(models.Model): name = models.CharField(...) ... class CompanyPost(models.Model): company = models.ForeignKey(Company,...) ... class PostA(CompanyPost): name = ... In a template I want to loop over posts of type A published by a specific company. I tried these variants: 1) {% for postA in company.companyposts_set.all.postA_set.all %} ... 2) {% for companyposts in company.companypost_set.all %} {% for postA in companyposts.postA_set.all %} ... {% endfor %}{% endfor %} I tried other sub-variants of the above. None seems to work. I know that I can easily prepare the set in the view, like: postsA = PostA.objects.filter(company__pk=pk) And pass postsA to the template context, but I'm wondering whether there is a way to loop over related models' children in the template. (note: looping over companyposts works. But I get of course all types of posts, like postB etc.: {% for post in company.companypost_set.all %} That is why I tried variant 2) above to loop again over the results.) Thank you in advance. UPDATE: Thank you all for your answers. I understand that, by choosing model inheritance, I chose a convoluted solution. In the present post I'm asking whether displaying related model's children in a template is possible. In order not to confuse questions, in this question I explain why I used concrete model inheritance and ask what would be a better solution.
If you don't want to define it in the views, you could define it as a property of Company objects. #property def post_a_set(self): return PostA.objects.filter(company__pk=self.pk) I'm pretty sure it's the model inheritance that is causing the problems, and dimly remember seeing something like his documented. I'd echo, do you really need concrete model inheritance here? Other approaches are wider CompanyPost objects with a post_type choices field and other fields null or blank if inappropriate; or a post_type field and the data that applies only to that type stored as a JSON string or (if you are using Postgresql) a JSONField.
How do I display Django ManyToMany on a template? Simple code needed
I am trying to display ManyToMany field on the template in reversed order. Here is what I mean: I managed to display ManyToMany field on template when ManyToMany field was a field in model used so for example: <br/>{% for tag in post.tag.all %}{{ tag }}<br/>{% endfor %} will display all of the tags(meaning categories) that the post belongs to based on this model: class Post(models.Model): tag = models.ManyToManyField(Tag,blank=True,null=True,related_name='tag') Now I want something opposite - display authors of the post when ManyToMany field is in the Author model (Post model above stays the same): class Person(models.Model): post=models.ManyToManyField(Post,blank=True,null=True,related_name='post') I am quite sure it has something to do with Related Object Reference ( https://docs.djangoproject.com/en/2.2/ref/models/relations/) Just can not make it work. I have tried the following on the template. {% for post in posts %} {% for author in post.person_set.all %}{{author}}<br/>{% endfor %} {% endfor %} Also, shall I do this kind of searches on the template like above or is it a better practice put this kind of searches in views...resourcewise. Thanks for help.
You have a misunderstanding on what the related_name= parameter [Django-doc] does. Like the documentation says: The name to use for the relation from the related object back to this one. (...) So it is the name of the relation in reverse. In order to make your models "sound", you thus should name it like: class Person(models.Model): posts = models.ManyToManyField(Post, blank=True, null=True, related_name='authors') It also makes sense here to use the plural, so posts instead of post. In that case, you thus can render this with: {% for post in posts %} {% for author in post.authors.all %}{{author}}<br/>{% endfor %} {% endfor %} Note that if you want to render all the values for ManyToManyFields, you better use .prefetch_related(..) in the queryset to prefetch the Person,s otherwise rendering the template will result in a lot of extra queries.
Django templates - Check if user already has created an object for another object
I have 2 models called Valuation and Assessment. A valuation has many assessments (foreignkey relationship). Users can only create 1 assessment for each valuation. This seems to be very simple but I can't wrap my head around it. I need to check if any of a valuation's existing assessments belongs to the request.user, how do I do that? this doesn't work because assessment_set.all is a list. (assessments in this case is a list of assessments for the currently displayed valuation) {% if request.user.assessment_set.all not in assessments %} # Display "make an assessment" form {% endif %} So I think I'd need to loop over request.user.assessment_set.all and see if each of the user's assessments is in the assessments list, but I feel like that is very inefficient and there must be a better way. Advice?
Based on your description I assume you have the following model architecture(i have used related_name for the reverse relationships), class Valuation(models.Model): # fields class Assessment(models.Model): #fields user = models.ManyToManyField(User, related_name='assessments') valuation = models.ForeignKey(Valuation, related_name='assessments') So if you want to limit the logged in user to create only 1 assessment for each valuation, then you present only those valuations that are not assessed. views.py unassessed_valuations = Valuation.objects.exclude(assessments__user=request.user) template {% for valuation in unassessed_valuations %} valuation assessment form {% endfor %}
django get list of distinct 'children' of ForeignKey related model (and do this in template?)
I'm making a database of released music albums models.py class Image(models.Model): image = models.ImageField(.... class Album(models.Model): title = models.CharField(.... class Release(models.Model): album = models.ForeignKey(Album) cover_art = models.ForeignKey(Image, blank=True, null=True, on_delete=models.SET_NULL) In my template (at the moment I'm using generic views) I have: {% for a in album_list %} {% for r in a.release_set.all %} {% if r.cover_art %} # display cover art image {% endif %} {% endfor %} {% endfor %} The problem is that sometimes an album has been released several times with identical cover art, in which case I'd like to display the image only once, with some text listing the releases it pertains to. I've tried: {% for i in a.release_set.cover_art %} {% for i in a.release_set.cover_art_set %} {% for i in a.release_set.all.cover_art %} {% for i in a.release_set.all.cover_art_set %} Or in a simpler case, I'd at least like to display the images smaller if there are more than one of them. {% if a.release_set.count > 1 %} # works but displays duplicate images {% if a.release_set.cover_art_set.count > 1 %} # doesn't work (see above) Is it possible to get a list of objects related by reversing this ForeignKey lookup then asking for the set of their children? The only way I can think of is by assembling some tuples/lists in the view.
I managed this with a new method on the Album model: class Album(models.Model): title = models.CharField(.... def distinct_cover_images(self): "Returns the queryset of distinct images used for this album cover" pks = self.release_set.all().values_list('cover_art__pk', flat=True) distinct_cover_images = Images.objects.filter(pk__in=pks).distinct() return distinct_cover_images Then the template is much more simple: {% for i in a.distinct_cover_images %} Credit to #danilobargen however for his contribution to this code.
If I understood this right: An album can have several releases A release has only one cover You want to loop over all covers of an album In that case, the following should work: {% for release in a.release_set.all %} {{ release.cover_art.image }} {% endfor %} If you want to prevent listing identical covers, you can either compare the covers in the loop, or prepare a set with distinct covers in your view, so you can pass it on to the template. # Solution using a set context['distinct_coverimages'] = \ set([r.cover_art.image for r in album.release_set.all()]) # Solution using two queries, might perform better pks = album.release_set.values_list('cover_art__pk', flat=True) context['distinct_coverimages'] = models.Image.filter(pk__in=pks).distinct() A third alternative would be creating a custom template filter for your album, to return all distinct release covers. In any case, I recommend debugging such things in your Django shell. You can issue the shell with ./manage.py shell. If you have installed django-extensions, you can also use ./manage.py shell_plus to autoload all models. All object attributes and functions that don't require arguments (e.g. normal instance attributes or instance functions without arguments like 'string'.isalnum()) can also be used the same way (just without the parentheses) in your template.
Widget to display categories as tree in Django admin
is there any existing code to display the list of some Category object in the admin of a Django site? Ideally I'd like some tree representation like cat_1 ----cat_1_1 ----cat_1_2 cat_2 cat_3 ----cat_3_1 ----cat_3_2 ----cat_3_3 with up/down button to change the order. class Category(models.Model): parent = models.ForeignKey('self', null=True, related_name='children') name = models.CharField(max_length=100)
Firstly, you haven't actually defined an order on this model - just a parent. You need something like MPTT which keeps track of the level and the position within that level. Given that, it's fairly easy to write a __unicode__ method for the model which displays the number of hyphens equal to the category's level: def __unicode__(self): return '%s%s' % ('-' * self.level, self.name) As for the up/down button, you'll need to write that in Javascript, I expect.
Here is one widget, based on django-mptt : http://anentropic.wordpress.com/2009/11/05/more-django-mptt-goodness-filteredselectmultiple-m2m-widget/ looks like what you're looking for
There is a django-categories package, which add a select box with all category to your admin site to manage categories.
It's very simple All you have to do in your view is get all objects: categories = Category.objects.all() Then in your template : {% for category in categories %} <li>- {{category.name}} </li> {% for child in category.children.all %} <ul>* {{child.nom}} </ul> {% endfor %} </li> {% endfor %}
Generating the desired tree like structure requires "Modified Preorder Tree Traversal" of the tabular data. A custom implementation will be fairly complex. You can use existing package called Django MPTT to simplify the hierarchical tree generation based on your Category model. With it you can achieve following: get descendants of a node get ancestors of a node get all nodes at a given level get leaf nodes