How can I eagerly load an answers latest revision? - django

I have a Q&A site and it allows answers to be changed while storing a revision of each change, much like here on Stackoverflow. When selecting some answers, I'd like it to include the most recent(but not all) revision as a property of each answer, so that I can avoid doing n+1 queries on my page.
class Answer(models.Model):
user = models.ForeignKey(User)
problem = models.ForeignKey(Problem)
class AnswerRevision(models.Model):
answer = models.ForeignKey(Answer, related_name='revisions')
text = models.TextField()
timestamp = models.DateTimeField()
Answer.objects.filter(problem=p)

I'd suggest the following structure:
class Answer(models.Model):
user = models.ForeignKey(User)
revision = models.ForeignKey(AnswerRevision)
problem = models.ForeignKey(Problem)
class AnswerRevision(models.Model):
answer = models.ForeignKey(Answer, related_name='revisions')
text = models.TextField()
timestamp = models.DateTimeField()
The only difference is that I added a revision property to the answer. When a new revision is made, just change the FK to point to the new revision.

Related

Deleting related objects deletes the ForeignKey object

I have the following models:
class User(models.Model):
user_question = models.ForeignKey(UserQuestion)
class Question(models.Model):
text = models.CharField(max_length=255)
class UserQuestion(models.Model):
answer = models.CharField(max_length=255)
question = models.ForeignKey(Question)
user = models.ForeignKey(User, related_name='questions')
When I run the query below the user model is also deleted
user.questions.all().delete()
Is there any way to delete the questions without deleting the user?
I tried iterating over the questions and that didn't work
questions = user.questions.all()
for an in questions:
answer.delete()
I thought the queryset was lazy so maybe I needed to evaluate it before deleting so I printed it and this did not work.
print questions
questions.delete()
I know that making the ForeignKey nullable would provide me with methods like clear and remove but I did not want to do this because I did not want any orphaned user questions.
I updated the ForeignKey as follows
class UserQuestion(models.Model):
answer = models.CharField(max_length=255)
user = models.ForeignKey(User, related_name='questions', null=True, on_delete=models.SET_NULL)
I ran makemigrations and migrate but when I ran the query below The question model was still deleted.
user.questions.all().delete()
question = models.ForeignKey(Question, related_name='answers', on_delete=models.SET_NULL, null=True)
untested, but should work.
Your issue is a DB related issue, when deleting a foreignkey the DB will try to delete the related row.
Read more on the other options on this great answer.

Sort by related field in Django ORM

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.

Complex Query for Django ORM

I'm trying to execute a complex query using Django's ORM and I can't seem to find a nice solution. Namely, I have a web application where users answer questions based on a video. I need to display all the videos for a specified user that have at least one question unanswered (not responded to). I haven't been able to figure it out yet with the ORM ... I know that I could probably write a SQL query for this and just execute it with the raw SQL function, but I really would prefer to stay in the ORM.
Models: Video, Question, Response and default User.
Relationships:
Question has a many to many relation towards video
Response has a foreign key each to Question, Video and User
What the query needs to do:
Display all the videos for a specified user that have at least one video question unanswered (not responded to).
Any help would be awesome! I've been struggling with this for way too long.
EDIT: The models I have are (simplified):
class Video(TimeStampedModel):
title = models.CharField(max_length=200)
source_id = models.CharField(max_length=20)
class Question(TimeStampedModel):
DEMOGRAPHIC_QUESTION = 'd'
QUESTION_TYPES = (
(VIDEO_QUESTION, 'Video related question'),
(DEMOGRAPHIC_QUESTION, 'Demographic question'),
)
MULTIPLE_CHOICE = 0
PLAIN_TEXT = 1
RESPONSE_TYPE = (
(MULTIPLE_CHOICE, 'Multiple Choice'),
(PLAIN_TEXT, 'Plain Text')
)
type = models.CharField(max_length=1, choices=QUESTION_TYPES)
videos = models.ManyToManyField(Video, null=True, blank=True)
title = models.CharField(max_length=500)
priority = models.IntegerField()
class Response(TimeStampedModel):
user = models.ForeignKey(User)
question = models.ForeignKey(Question)
video = models.ForeignKey(Video, blank=True, null=True)
choice = models.ForeignKey(Choice, null=True, blank=True,related_name='selected_choice')
text = models.CharField(max_length=500, blank=True)
// Not relevant but included for clarity
class Choice(TimeStampedModel):
question = models.ForeignKey(Question)
text_response = models.CharField(max_length=500)
image = models.FileField(upload_to=_get_choice_img_path, blank=True)
value = models.IntegerField(default=0)
external_id = models.IntegerField(default=0)
Judging logically by the way your models look like, I think something close to the following should be fine.
q = Response.objects.select_related().filter(user__name=user).filter(response__choice=None)
videos = Video.objects.filter(id__in=q.extra(where=["{}>=1".format(q.count())]).values('video_id'))
Hope you understand what I did there. The first line basically tries to take a natural join of the model objects. The second line is using the query generated in the first line to get the count and checks if it is at least 1, and gets the Videos that belong to that query.

Django: implementing JOIN using Django ORM?

I have a Q&A type of site built in Django with the following models:
class Question(models.Model):
title = models.CharField(max_length=70)
details = models.TextField()
class Answer(models.Model):
question_id = IntegerField()
details = models.TextField()
I need to display a specific question together with its answers. Normally I'd need 2 queries to do that:
Question.objects.get(id=1)
Answer.objects.get(question_id=1)[:10]
I'm hoping to retrieve everything using one query. In MySQL it'd be:
SELECT *
FROM Question JOIN Answer ON Question.id=Answer.question_id
WHERE Question.id=1
LIMIT 10
Is there anyway I could do this through Django's ORM?
Would extra() help in this case?
This is exactly what select_related() does. The only gotcha is that
you have to start with the Answer model, rather than Question, but the
result is the same:
answers = Answer.objects.filter(question_id=1).select_related()
Now each answer object has a pre-fetched 'question' attribute, and
accessing it won't hit the db again.
Consider using models.ForeignKey(Question) instead of question_id = IntegerField().
This is the optimal (more relational) way to express the relationship between Questions and Answers you are trying to portray.
This way you can simply call Answers.objects.filter(question_id=<id>) and get exactly what you're looking for.
class Question(models.Model):
title = models.CharField(max_length=70)
details = models.TextField()
class Answer(models.Model):
question = models.ForeignKey('Question')
details = models.TextField()
id = <whatever_id>
answers = Question.objects.get(id=id).answer_set.all()
#Consider A Foreign Key Relationship Between Books And Publisher
class Publisher(models.Model):
name = models.CharField(max_length=100)
eclass Book(models.Model):
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
#Fetch Publisher Name For A Book
book = Book.objects.select_related('publisher').get(id=1)
book.publisher.name
#Fetch books which have specific publisher
publisher = Publisher.objects.prefetch_related('book_set').get(id=1)
books = publisher.book_set.all()
for more
https://kwikl3arn.com/django/JOINS

Filtering on related objects when using annotations in Django

Imagine for a moment that I have a system that allows users to take notes. I've got a User model and a Note model:
class User(models.Model):
name = models.TextField()
class Note(models.Model):
contents = models.TextField()
user = models.ForeignKey(User)
created_at = models.DateTimeField(auto_now_add=True)
I'd like to get a rank ordered number of notes in the last 24 hours. Currently I'm doing this:
User.objects.filter(note__created_at__gt=datetime.now()-timedelta(days=2)).annotate(note_count=Count('note')).order_by('-note_count')
This gives the same result whether I do timedelta(days=2) or timedelta(seconds=1)
Any help would be appreciated.
It turns out this method actually works, it was a bug elsewhere that was causing problems with output.