Given these models:
class Profile(models.Model):
name = models.CharField(max_length=50)
class BlogPost(models.Model):
name = models.CharField(max_length=50)
created_by = models.ForeignKey(Profile, related_name='posts')
class Comment(models.Model):
blog = models.ForeignKey(BlogPost, related_name='comments')
body_text = models.TextField()
created_by = models.ForeignKey(Profile, null=True, blank=True, on_delete=models.SET_NULL)
Given a profile, I want to find all of the blog posts created by that profile, where there are either no comments, or only the creator of the post has commented.
For example:
profile = Profile.objects.get(id={id})
profile.posts.exclude(~Q(comments__created_by=profile))
I thought .exclude(~Q(comments__created_by=profile) would exclude all posts where a comment exists that has been created by someone other than the profile, but that's not working out. (It is finding posts where the created_by is null, and also posts where the profile has commented along with other users - which I'm trying to exclude from the set)
What you need is this:
comments_by_others_in_profile_posts = Comment.objects \
.filter(blog__created_by=profile) \
.exclude(created_by=profile)
profile.posts.exclude(comments=comments_by_others_in_profile_posts)
You can also try it like this (i believe this way it can be a little bit faster, but need to see the queries EXPLAIN output):
profile.posts.exclude(id__in=comments_by_others_in_profile_posts.values_list('blog', flat=True))
Well you were almost there, just need to include the conditions from your instincts. A good way to go about this is to use the django shell and a bunch of test data that matches your permutations. For more complex queries, its a good idea to write a unit test first.
profile.posts.filter(Q(comments__isnull=True)|~Q(comments__created_by=profile, comments__created_by__isnull=False))
Related
I'm trying to access the grandchildren records in a list to avoid duplicate records. In this example, a tag can only be used once across articles for a given author. I will use the resulting list of grandchildren records in my clean function to return validation errors.
class Article(models.Model):
tag = models.ManyToManyField(Tag)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
class Tag(models.Model):
class Author(models.Model):
Right now I can do this:
print(author.articles.first().tag.first())
Travel
I'd like to be able to use something like author.articles.tags.all() to return the list and check the submitted form against it to raise a ValidationError message to the user.
How can this be done efficiently with the basic Many-to-Many setup without creating an intermediate table for the tag relationships? This is solely in the Admin interface, in case that matters at all.
i come from the link you posted on upwork,
the way i understand your question,
what you want to achieve seems to be impossible ,
what i think can work , is to fetch articles related to the author, with their corresponding tags,
after that they are retrieved you do filtering and remove duplicates.
otherwise the tag has nothing to connect it with the author,,
I'm so jealous that's a great answer #since9teen94
Be aware, I will not base my answer in the easiest solution but how we model reality (or how we should do it).
We put tags after things exists right?
In that order I will make your life horrible with something like this:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=30, null=False)
class Article(models.Model):
name = models.CharField(max_length=30, null=False)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
class Tag(models.Model):
name = models.CharField(max_length=30, null=False, unique=True)
articles = models.ManyToManyField(Article)
but believe me you don't want to follow this approach it will make your work harder.
You can search for Articles based on Tags directly
Tag.objects.filter(name='Scify').first().articles.all() # name is unique
The real issue with this is that the reverse lookup is really complex (I mean.. get ready for debug)
Article.objects.filter(
id__in=list(
Tag.objects.filter(name='Scify').first().articles.values_list('id', flat=True)
)
)
I am sure this does not solve your problem and I don't like complex code for no reason but if you're open to suggestions I don't mind to think different and add some options
Edit:
About the author and clean repeated tags.. well you don't have to deal with that and if you want to find all Tag your author has you could loop the tags
for tag in Tag.objects.all():
if tag.articles.filter(author__name='StackoverflowContributor'):
print(tag.name)
# > Scify
I am just saying that there are options not that this is the best for you but don't be afraid of the terminal, it's really cool
The Django ORM is pretty cool when you get used to it, but regular SQL is pretty cool too
# created_at, updated_at, name and tag_val are variables I
# added due to my slight ocd lol
class Author(models.Model):
name = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Tag(models.Model):
tag_val = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Article(models.Model):
author = models.ForeignKey(Author,related_name='articles', on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag, related_name='articles')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
I can write my query like this, assuming the variable 'author' has been assigned an Author object instance, and get a list of dictionaries [{'tags':1},{'tags':2}] where the value is the auto generated primary key id of the Tag object instance
author.articles.values('tags').distinct()
I am having trouble deciding how to structure my models for a particular data structure.
The models I have would be Posts, Groups, Users.
I want the Post model that can be posted from a groups page or user page and potentially more, like an events page.
Posts would contain fields for text, images(fk), user, view count, rating score (from -- a reference to where ever it was posted from like user or group page, though I am unsure how to make this connection yet)
I thought about using a Generic Foreign Key to assign a field to different models but read articles suggesting to avoid it. I tried the suggested models, but I wasn't unsure if they were the right approach for what I required.
At the moment I went with Alternative 4 - multi-table inheritance
class Group(models.Model):
name = models.CharField(max_length=64)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='_groups')
members = models.ManyToManyField(
settings.AUTH_USER_MODEL)
def __str__(self):
return f'{self.name} -- {self.created_by}'
def save(self, *args, **kwargs):
# https://stackoverflow.com/a/35647389/1294405
created = self._state.adding
super(Group, self).save(*args, **kwargs)
if created:
if not self.members.filter(pk=self.created_by.pk).exists():
self.members.add(self.created_by)
class Post(models.Model):
content = models.TextField(blank=True, default='')
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="%(app_label)s_%(class)s_posts",
related_query_name="%(app_label)s_%(class)ss")
# class Meta:
# abstract = True
def __str__(self):
return f'{self.content} -- {self.created_by}'
class PostImage(models.Model):
image = models.ImageField(upload_to=unique_upload)
post = models.ForeignKey(
Post, related_name='images', on_delete=models.CASCADE)
def __str__(self):
return '{}'.format(self.image.name)
class UserPost(models.Model):
post = models.OneToOneField(
Post, null=True, blank=True, related_name='_uPost', on_delete=models.CASCADE)
class GroupPost(models.Model):
post = models.OneToOneField(
Post, null=True, blank=True, related_name='_gPost', on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
To do some specific filters ex:
Filter specific group post
Post.objects.filter(_gPost__group=group)
Filter specific user post
Post.objects.filter(created_by=user) # exclude groups with _gPost__isnull=False
Create post to user/group
p = Post.objects.create(...)
up = UserPost.objects.create(post=p)
gp = GroupPost.objects.create(post=p)
Really I am wondering if this is a sensible approach. The current way of a filter and creating feel odd. So only thing making me hesitant on this approach is just how it looks.
So, is Generic ForeignKey the place to use here or the current multi-table approach. I tried going with inheritance with abstract = True and that was unable to work as I need a foreign key to base post model. Even with no abstract, I got the foreign key reference, but filter became frustrating.
Edit:
So far only weird issues(but not really) are when filtering I have to be explicit to exclude some field to get what I want, using only .filter(created_by=...) only would get all other intermediate tables.
Filter post excluding all other tablets would requirePost.objects.filter(_uPost__isnull=True, _gPost__isnull=True, _**__isnull=True) which could end up being tedious.
I think your approach is sensible and that's probably how I would structure it.
Another approach would be to move the Group and Event foreignkeys into the Post model and let them be NULL/None if the Post wasn't posted to a group or event. That improves performance a bit and makes the filters a bit more sensible, but I would avoid that approach if you think Posts can be added to many other models in the future (as you'd have to keep adding more and more foreignkeys).
At the moment I will stick with my current pattern.
Some extra reading for anyone interested.
https://www.slideshare.net/billkarwin/sql-antipatterns-strike-back/32-Polymorphic_Associations_Of_course_some
I'm currently working on a website (with Django), where people can write a story, which can be upvoted by themselves or by other people. Here are the classes for Profile, Story and Upvote:
class Profile(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=30, null=True)
last_name = models.CharField(max_length=30, null=True)
class Story(models.Model):
author = models.ForeignKey('accounts.Profile', on_delete=models.CASCADE, related_name="author")
title = models.CharField(max_length=50)
content = models.TextField(max_length=10000)
class Upvote(models.Model):
profile = models.ForeignKey('accounts.Profile', on_delete=models.CASCADE, related_name="upvoter")
story = models.ForeignKey('Story', on_delete=models.CASCADE, related_name="upvoted_story")
upvote_time = models.DateTimeField(auto_now=True)
As you can see, Upvote uses two foreign keys to store the upvoter and the related story. Now I want to make a query which gives me all the stories, sorted by the amount of upvotes they have. I've tried my best to come up with some queries myself, but it's not exactly what I'm searching for.
This one doesn't work at all, since it just gives me all the stories in the order they were created, for some reason. Also it contains duplicates, although I want them to be grouped by story.
hot_feed = Upvote.objects.annotate(upvote_count=Count('story')).order_by('-upvote_count')
This one kind of works. But if I'm trying to access a partical story in my template, it just gives me back the id. So I'm not able to fetch the title, author and content from that id, since it's just an integer, and not an object.
hot_feed = Upvote.objects.values('story').annotate(upvote_count=Count('story')).order_by('-upvote_count')
Could someone help me out with finding the query I'm searching for?
You are querying from the wrong model, you here basically fetch Upvotes ordered by the number of stories, or something similar.
But your probaby want to retrieve Storys by the number of upvotes, so you need to use Story as "queryset root", and annotate it with the number of upvotes:
Story.objects.annotate(
upvote_count=Count('upvoted_story')
).order_by('-upvote_count')
I think the related_name of your story is however a bit "misleading". The related_name is the name of the relation "in reverse", so probably a better name is upvotes:
class Upvote(models.Model):
profile = models.ForeignKey(
'accounts.Profile',
on_delete=models.CASCADE,
related_name='upvotes'
)
story = models.ForeignKey(
'Story',
on_delete=models.CASCADE,
related_name='upvotes'
)
upvote_time = models.DateTimeField(auto_now=True)
In that case the query is:
Story.objects.annotate(
upvote_count=Count('upvotes')
).order_by('-upvote_count')
I have a Notes and a NoteRefs fields where the NoteRefs has a foreign key to the Notes. I need to query the Notes but order by the related field (ie. the NoteRefs' start_ref field).
How might I do that through the django ORM? Here's kinda what works in SQL
SELECT
note.user_id,
note.content,
note.created,
note.modified
FROM noteref
INNER JOIN note
ON note.id = noteref.note_id
ORDER BY noteref.start_ref
I can't use Note.order_by('related_field'), because the related field isn't part of the Note Model. From what I can tell, that seems to be what the documentation says to do. How can I sort on the related field here?
EDIT: Model information
class Note(models.Model):
user = models.ForeignKey(User, db_index=True)
content = models.TextField()
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
class NoteRef(models.Model):
note = models.ForeignKey(Note, db_index=True)
_order = models.IntegerField(default=0)
book = models.IntegerField(max_length=2)
start_ref = models.IntegerField(max_length=8, db_index=True)
end_ref = models.IntegerField(max_length=8, db_index=True)
ref_range = models.IntegerField()
passage = models.CharField(max_length=50)
You should try Note.objects.order_by("noterefs__start_ref")
The documentation doesn't make this very clear, as it uses a ForeignKey to self, but it works.
Now, the docs also warn against the possibility of duplicate objects showing up if you have multiple NoteRefs for a single Note, so you should double-check this.
I want to sort a QuerySet of contacts by a related field. But I do not know how.
I tried it like this, but it does not work.
foundContacts.order_by("classification.kam")
Actually in a template I can access the kam value of a contact through contact.classification.kam since it is a OneToOne relationship.
The (simplified) models look like this:
class Classification(models.Model):
kam = models.ForeignKey(User)
contact = models.OneToOneField(Contact)
class Contact(models.Model):
title = models.ForeignKey(Title, blank=True, null=True)
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
It should be:
foundContacts.order_by("classification__kam")
Here is a link for the Django docs on making queries that span relationships: https://docs.djangoproject.com/en/dev/topics/db/queries/#lookups-that-span-relationships
You can also see some examples in the order_by reference:
https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.order_by
as the documentation indicates, it should be used as in queries about related models
https://docs.djangoproject.com/en/3.0/ref/models/querysets/#order-by
foundContacts.order_by("classification__kam")
I've struggled a lot with this problem, this is how I solved it:
contact ordered by "kam" (Classification) field:
show_all_contact = Contact.objects.all().order_by(title__kam)
classification ordered by Users email (no sense in it, but only show how it works):
show_all_clasification = Classification.objects.all().order_by(kam__email)