Loading tree of relations at once - python

I've got model called Post:
class Post(models.Model):
poster = models.ForeignKey(User)
content = models.TextField(verbose_name='Text', max_length=1000)
reply_to = models.ForeignKey('self', null=True, blank=True, default=None)
This allows to add 'first post' (with blank reply_to), and reply to post and even 'reply to reply'
For example I've got in my database something like that:
First Post
Reply one
Reply to reply one
Reply two
Reply to reply two
How to load that tree of replies?
When I use:
r = Post.objects.filter(reply_to=FirstPost)
It returns of course:
Reply one
Reply two
Is it possible to load all related posts at once?
I need it mainly to count all replies to first post.

You can use MPTT (http://django-mptt.github.com/django-mptt/tutorial.html#the-problem). I have not used this library before so let me know how it goes.
models.py
class Post(MPTTModel):
poster = models.ForeignKey(User)
content = models.TextField(verbose_name='Text', max_length=1000)
parent = models.TreeForeignKey('self', null=True, blank=True, related_name='children')
class MPTTMeta:
order_insertion_by = ['poster']
views.py
....
r = FirstPost.get_children()

No, I don't think there is a way to load all replies at once.
But, you can add extra metadata to the post type to be able to run a in-order-style query, where counting the number of replies becomes a simple calculation with data already loaded for the parents node.
See this article on how you could do that (it uses the MySQL SQL dialect, and PHP, but the principles still apply).
Basically, you add left and right fields to the nodes in your tree that define an ordering, letting you easily count the number of items below a given root element in the tree. It's like a Binary Tree in a database table. The principle is taken from this excellent database design book: "Joe Celko's Trees and Hierarchies in SQL for Smarties".

Related

User manually create model fields for post [django]

So I want to create a post by letting the user add a section/subtitle as they need it, but I have no idea how to go about creating this, I currently just have a title, preview and content block in a form but want the user to be able to create as many subtitle and content blocks as they want. Any help is very much appreciated :)
Well that's really basic database design question... What you describe is a "one to many" relationship (one "post" has many "subtitle-plus-content"), which is implemented using a table for the "subtitle-plus-content" records, with a foreign key on the "post" table (so we know which "subtitle-plus-content" records belong to which "post" record).
In Django, this is simply done by creatin a matching model:
class Post(models.Model):
title = models.CharField(max_lenght=xxx)
content = models.TextField()
# etc
class ContentBlock(models.Model):
""" A block with additionnal subtitle and content for posts """
post = models.ForeignKey(Post, on_delete=models.CASCADE)
subtitle = models.CharField(max_lenght=xxx)
content = models.TextField()
# etc
You'll find more documentation here

Implementing one to many between an article and page models in Wagtail

I'm trying to setup a Wagtail site with an article to pages structure but I'm struggling. A review article for example may have an introduction page, a benchmark page and a conclusion page. I want to work out how to allow this relationship in wagtail and have it so that editors can add multiple pages to the same article on the same page. I can imagine the pages interface looking a bit like how you have content, promote and settings on pages but with the ability to add, rename and reorder pages. I've tried using a foreign key on a page model that links to an article but I can't get it to be shown in the admin the way I want.
Here is the django version of model layout I was looking to use. You have a parent article that is then made up of one or multiple pages. The pages should be editable, orderable and be created from within one panel in the admin with streamfields:
Class Article(models.Model)
STATE_DRAFT = 0
STATE_REVIEW= 1
STATE_PUBLICATION = 2
STATE_HIDDEN = 3
​
STATE = (
(STATE_DRAFT, 'draft'),
(STATE_REVIEW, 'pending review'),
(STATE_PUBLICATION, 'ready for publication'),
(STATE_HIDDEN, 'hide and ignore'),
)
title = models.CharField(_('title'), max_length=256)
slug = models.SlugField(
_('slug'), unique=True, blank=True, default='', max_length=256
)
description = models.TextField(
_('description'), max_length=256, blank=True, default=''
)
author = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='article'
)
publication = models.DateTimeField(
null=True, blank=True, default=None, db_index=True, help_text='''
What date and time should the article get published
'''
)
state = models.PositiveIntegerField(
default=0, choices=STATE, help_text='What stage is the article at?'
)
featured = models.BooleanField(
default=False,
help_text='Whether or not the article should get featured'
)
​
class Page(Page):
article = models.ForeignKey(
'Article', on_delete=models.CASCADE, related_name='pages'
)
title = models.CharField(max_length=256)
number = models.PositiveIntegerField(default=1) # So pages are ordered
body = models.TextField(blank=True)
As per my comment I don't think you'll be able to achieve everything you're looking for short of implementing an entirely bespoke CMS - but if you're able to bend the UI and data modelling requirements, then Wagtail's RoutablePageMixin is one possible way of achieving the general pattern of editing an article as a single unit, while presenting it as multiple pages on the front-end.
In this approach, you'd make Article a Wagtail Page model, with all of the sub-page content defined as fields (or InlinePanel child models) on that model. (If you want to split the content entry into tabs within the editing interface, see Customising the tabbed interface, although this won't support dynamically adding / reordering them.) You'd then define a URL route and template for each subpage of the article:
from wagtail.core.models import Page
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
class ArticlePage(RoutablePageMixin, Page):
intro = StreamField(...)
main_page = StreamField(...)
conclusion = StreamField(...)
#route(r'^$')
def intro_view(self, request):
render(request, 'article/intro.html', {
'page': self,
})
#route(r'^main/$')
def main_page_view(self, request):
render(request, 'article/main_page.html', {
'page': self,
})
#route(r'^conclusion/$')
def conclusion_view(self, request):
render(request, 'article/conclusion.html', {
'page': self,
})
In this example the three sub-pages are hard-coded, but with some more work (perhaps an InlinePanel child model with a slug field and a StreamField) you could make the subpages dynamic.
I saw gasman already provided an answer to you question, but I'm still going to write up an answer for two reasons:
I think you need some more pointers as to why gasmans' proposal is a better solution than yours, but it's way to much to write in a comment.
I have implemented a similar solution before, where there is a top level 'Article'-like object with multiple reorderable child objects, where the actual content resides.
Why you should make Article a Page subclass
You chose not to make Article a subclass of Page, and you said it was because the Article itself does not contain any content, only metadata about an article. That is not a very strange thought process, but I think you're looking at the wrong requirements for your Article model.
Let's look at Wagtail's own Page model. What kind of functionality does it provide out of the box?
It provides a tree structure with parent and child pages, so that your page can be placed somewhere in the hierarchy of your website
It provides a slug_field, so that Wagtail can automatically handle linking to your page.
It provides functionality for drafting, publishing and unpublishing.
Wagtail doesn't dictate anything about content, leaving you to decide what kind of content you want to put on your Page subclass, if any. Examples of Pages that do not have a body would be:
Contact forms.
Blog index pages.
Good questions you could ask when deciding whether you want a Model to be a subclass of a Page are:
Do I want this object to have it's own url?
Do I want to be able to place this object somewhere inside my website hierarchy?
Do I want to have SEO advantages for the object?
Do I want to be able to publish/unpublish this object or not?
In your case of the Article, you could say yes to almost al these question, so it'd be wise to make it a Page subclass. That way, you don't have to reinvent the wheel.
How you define the actual 'body' of your page is up to you.
You can place the actual content in either snippets, or subpages to that Article. Or you can even choose to create a list of StreamFields inside your model.
How to implement ordered subcontent.
I have implemented a structure like this before.
The way I did this was very similar to what gasman proposes.
In my case, I needed to create a website where you could find an object (like you article) and display different types of explanation modules for it. For each document, I created a ArticlePage, and for each explanation module, I created a snippet called ExplanationModule.
I then created a through model with an ordering, and added a RoutablePageMixin to the class like gasman explains.
The structure looks something like this:
#register_snippet
class ArticlePageModule(models.Model):
...
title = models.CharField(max_length=100)
body = StreamField(LAYOUT_STREAMBLOCKS, null=True, blank=True)
panels = [
FieldPanel('title'),
StreamFieldPanel('body'),
]
class ArticlePageModulePlacement(Orderable, models.Model):
page = ParentalKey('articles.ArticlePage', on_delete=models.CASCADE, related_name='article_module_placements')
article_module = models.ForeignKey(ArticlePageModule, on_delete=models.CASCADE, related_name='+')
slug = models.SlugField()
panels = [
FieldPanel('slug'),
SnippetChooserPanel('article_module'),
]
class ArticlePage(Page, RoutablePageMixin):
# Metadata and other member values
....
content_panels = [
...
InlinePanel('article_module_placements', label="Modules"),
]
#route(r'^module/(?P<slug>[\w\-]+)/$')
def page_with_module(self, request, slug=None):
self.article_module_slug = slug
return self.serve(request)
def get_context(self, request):
context = super().get_context(request)
if hasattr(self, 'article_module_slug'):
context['ArticlePageModule'] = self.article_module_placements.filter(slug = self.article_module).first().article_module
return context
What this does is the following:
Create a ArticlePageModule snippet, which is just some kind of content, like a title and a body.
Create a ArticlePageModulePlacement which links a ArticlePage to a module, and adds the following:
A slug
An Ordering (Because it subclasses the Orderable mixing)
Create a ArticlePage which does two things:
Define a ArticlePageModuleplacement panel, which allows you to add ArticlePageModulePlacements
Subclass RoutablePagemixin, as described in gasman's answer.
This provides you with a Wagtail-proof, reusable and robust way of creating Articles with SubContent.
The modules don't show up in tabs, but will be shown on the page's layout page under a panel called 'Modules'.

Getting author of last post

So I have two models: Thread, ForumPost
With a many-to-one relationship (each Thread can have many ForumPosts).
I would like to display the author of the last post made into that thread.
I'm currently doing:
threads = Thread.objects.annotate(replies=Count('forumpost'), lastpost=Max('forumpost'))
And I can access it in my view by doing:
<td>{{thread.lastpost}}</td>
This works fine - but it doesn't do what I want - this only displays the last post ID (which is logical, because I'm displaying the post), not the author. So when I try to do:
<td>{{thread.lastpost.author}}</td>
It won't display the author (it's blank), although I have
author = models.CharField(max_length=200)
In my ForumPost model. So how do I go on about displaying the author, not the ID?
Models:
class Thread(models.Model):
title = models.CharField(max_length=50)
class ForumPost(models.Model):
thread = models.ForeignKey(Thread, on_delete=models.CASCADE)
author = models.CharField(max_length=200)
text = models.CharField(max_length=50)
You need a subquery expression.
newest = ForumPost.objects.filter(post=OuterRef('pk')).order_by('-pk')
threads = Thread.objects.annotate(last_post_author=Subquery(newest.values('author')[:1]))
Now each thread has a last_post_author attribute with the value of the author of the last post.
Note, you should really have a more specific field to order on - pk will usually be chronological, but it's not guaranteed. Add a created_at field with auto_now_add and order by that in the subquery.

How can I correct my ORM statement to show all friends not associated with a user in Django?

In my Django application, I've got two models, one Users and one Friendships. There is a Many to Many relationship between the two, as Users can have many Friends, and Friends can have many other Friends that are Users.
How can I return all friends (first and last name) whom are NOT friends with the user with the first_name='Daniel'?
Models.py:
class Friendships(models.Model):
user = models.ForeignKey('Users', models.DO_NOTHING, related_name="usersfriend")
friend = models.ForeignKey('Users', models.DO_NOTHING, related_name ="friendsfriend")
created_at = models.DateTimeField(blank=True, null=True)
updated_at = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = 'friendships'
class Users(models.Model):
first_name = models.CharField(max_length=45, blank=True, null=True)
last_name = models.CharField(max_length=45, blank=True, null=True)
created_at = models.DateTimeField(blank=True, null=True)
updated_at = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = 'users'
So far, here's what I've tried in my controller (views.py) -- please note, I understand controllers should be skinny but still learning so apologies. What I tried in the snippet below (after many failed attempts at a cleaner method) was to try and first grab friends of daniels (populating them into a list and then removing any duplicate ids), and then filter them out by their id.
# show first and last name of all friends who daniel is not friends with:
def index(req):
friends_of_daniel = Friendships.objects.filter(user__first_name='Daniel')
daniels_friends = []
for friend_of_daniel in friends_of_daniel:
daniels_friends.append(friend_of_daniel.friend.id)
daniels_friends = list(set(daniels_friends))
not_daniels_friends = Friendships.objects.exclude(id__in=daniels_friends)
context = {
'not_daniels_friends':not_daniels_friends,
}
return render(req, "friendapp/index.html",context)
However, when I try the following in my views (templates) file, I still see individuals whom are friends of Daniels. Any idea what I'm doing wrong?
<ul>
{% for not_daniel_friend in not_daniels_friends %}
<li>{{ not_daniel_friend.user.first_name }} {{ not_daniel_friend.user.last_name }}</li>
{% endfor %}
</ul>
I guess something like this will do. Just then take the list users, and get the first and last name of those users.
daniels = Users.objects.filter(first_name="Daniel") # There may be more than one Daniel
users = Friendships.objects.exclude(friend__in=daniels)
Note here, while Friendships.friend is a foreignkey of type Users you can pass Users instances (i.e daniels list) in friend__in to exclude those users.
Try this,In the place of friend_of_daniel.friend.id , You should exclude the results from User model.
Something like this :
def index(req):
friends_of_daniel = Friendships.objects.filter(user__first_name='Daniel')
daniels_friends = []
for friend_of_daniel in friends_of_daniel:
daniels_friends.append(friend_of_daniel.friend.id)
daniels_friends = list(set(daniels_friends))
not_daniels_friends = Users.objects.exclude(id__in=daniels_friends)
context = {
'not_daniels_friends':not_daniels_friends,
}
return render(req, "friendapp/index.html",context)
Thanks.
Firstly as a general comment: a cleaner way of populating a list of ids is using the .value_list() method from django (part of the .values() method in previous versions of Django). It has a "flat" flag that creates the list you want.
So, instead of:
friends_of_daniel = Friendships.objects.filter(user__first_name='Daniel')
daniels_friends = []
for friend_of_daniel in friends_of_daniel:
daniels_friends.append(friend_of_daniel.friend.id)
daniels_friends = list(set(daniels_friends))
You could do, in one line:
daniels_friends = Friendships.objects \
.filter(user__first_name='Daniel') \
.distinct('friend') \
.values_list('friend', flat=True)
distinct makes the same as your list() - set() cast (it makes sure that your list has no repeated elements) and values_list with flat=True can be customizable to any field in the related "user" table: .values_list('friend__id', flat=True) or .values_list('friend__first_name', flat=True) to get a list of first_names of Daniel's friends.
Coming back to your general question, you can do the whole query directly in one line using your related_names, as I am not really sure of what you want (an user instance, a Friendship instance or just a list of firsts and last names) I will give you many options:
If you want a Friendship instance (what you are trying in your sample
code):
friendships_not_friends_with_daniel = Friendships.objects\
.exclude(friend__first_name="Daniel")
This is equivalent to what #Rafael proposes in his answer:
daniels = Users.objects.filter(first_name="Daniel") # There may be
more than one Daniel users =
Friendships.objects.exclude(friend__in=daniels)
Here I am embedding his first query in the exclude by referencing the
field in the related table with double underscore (which is an very
powerful standard in Django).
If you want an User instance:
users_with_no_friendship_with_daniel = Users.objects\
.exclude(usersfriend__friend__first_name="Daniel")
Here you are using the related name of your model to access from the
users table to the friendships table, and then check if the friend of this user is called Daniel. This way of querying is a bit complex to understand but as soon as you get used to it becomes really powerful because it is very similar to the spoken language: you want all users, but excluding the ones that have a friendship, whose friend's first name is Daniel. Depending on how many friends an user hat or how many users are called Daniel, you might to add some distinct() methods or split the query in two.
As an advice, maybe you could improve the related name in your model, because it is what you would use if you have an user instance and want to get the related friendships: user_instance.friendships instead of user_instance.usersfriend and user_instance.friendsfriendships instead of user_instance.friendsfriend.... Do not know, it is always difficult to me to choose good related names...
If you want a list of tuples of users first and last names:
names_of_users_with_no_friendship_with_daniel = Users.objects\
.exclude(usersfriend__friend__first_name="Daniel")\
.values_list('first_name', 'last_name')
I am sorry if something is not clear, please ask and I try to explain better. (I am quite new in stackoverflow)

What's the best django way to do a query that spans several tables?

I have a reviews/ratings web application, a la Digg. My django app content has the following model:
class Content(models.Model):
title = models.CharField(max_length=128)
url = models.URLField(max_length=2048)
description = models.TextField(blank=True)
class Recommendation(models.Model):
user = models.ForeignKey(User)
content = models.ForeignKey(Content)
review = models.TextField()
rating = models.PositiveSmallIntegerField()
class Meta:
unique_together = ('user', 'content')
class Subscription(models.Model):
subscriber = models.ForeignKey(User, related_name='subscription_set')
publisher = models.ForeignKey(User, related_name='publication_set')
class Meta:
unique_together = ('subscriber', 'publisher')
I want to construct a page with all the recommendations of all the users to whom a current user (request.user) subscribes.
If I write this in SQL, I believe I'll end up with a query similar to the following:
select content_content.*, content_recommendation.*, auth_user.*
from content_content, content_recommendation, content_subscription, auth_user
where content_content.id = content_recommendation.content_id
and content_recommendation.user_id = content_subscription.publisher_id
and content_subscription.subscriber_id = ?
and auth_user.id = content_subscription.publisher_id;
How would I express this using Django's query APIs? I've read the docs, but just can't get my head around it.
I would use:
Recommendation.objects.filter(user__publication_set__subscriber=request.user).select_related()
That will get you all the Recommendation objects as you requested, and the select_related will load all the related User and Content objects into memory so that subsequent access of them won't hit the DB again.
How you'd contruct this query really has a lot to do with your handling of the returned data afterwards though. It may be more or less efficient to go one way vs. another based on what you do with it.alt text http://sonicloft.net/im/52
I think that it's:
Content.objects.filter(recommendation_set__user__publication_set__subscriber__pk=request.user.pk)/.distinct()/
or
Recommendation.objects.filter(user__publication_set__subscriber__pk=request.user.pk)/.distinct()/
-- depending on instances of which model you want to get. Distinct() might be needed to avoid duplicates.

Categories