Looking for proper behavior - django

my models:
class Question(models.Model):
content = models.CharField(max_length=160)
class Answer(models.Model):
content = models.CharField(max_length=260)
question = models.ForeignKey(Question)
And if i want all answers related to particular question, but hiting database as little as possible, should i use select_related like that:
q = Question.objects.select_related(depth=1).get(id=id)
answers = q.answer_set.all()
or just:
q = Question.objects.get(id=id)
answers = q.answer_set.all()
?

For answering this type of question, consider installing the Django Debug Toolbar. It provides information on all DB queries performed to process a request, including the amount of time each query takes.
So install the toolbar in your project, and browse to a page on your site that performs the query. Click on the toolbar to see query information. Try it both ways to see which is better.

Related

How to check if a object has ManyToMany reference in a queryset django?

I have these models in my django project
class Question(models.Model):
heading = models.CharField(max_length=300)
description = models.TextField()
class Profile(models.Model):
name = models.CharField(max_length=100,null=False,blank=False)
completed = models.ManyToManyField(Question)
I'm trying to find a optimized way for fetching a list of questions for user it should with whether they have completed it or not.
I think it can be done with these quersets:-
all_questions = Question.objects.all()
completed_questions = Question.objects.filter(profile = profile_instance)
where profile_instance is a profile object. Then we can loop through the all_questions and check whether they also exist in completed_questions, but i am not sure whether this is optimized for pagination. I'm using Django-Rest Framework pagination.
Edit 1:-
It looks like i'm not clear with the result i'm looking for, i will elaborate on it. I need a queryset which satisfies two requirements:-
List of all questions.
In this list, if let's say question 1 is completed by the user, then, it should have a boolean field turned to true. Note, that the question will only be completed for the current logged-in user not all the users.
If you are just trying to get the questions connected to the profile then something like this would work:
completed_questions = profile.completed.all()
This will return a queryset of Question objects connected to that profile. There isn't then the need to loop through all of the questions to see if they exist in completed questions.
If you want to filter Questions to get all of those that are (or aren't) answered at all then you can filter for profiles with completed questions and then filter the questions against it:
profile_with_completed = Profile.objects.exclude(completed=None)
completed_questions = Question.objects.filter(profile__in=profile_with_completed)
Edit
To get a list of all questions annotated with whether the question has been completed by the user or not, use the following annotation. It will check whether the primary key of each question is in the list of completed question primary keys connected to the selected profile (which you can separately linked to the logged-in user).
from django.db.models import Case, When, Value, BooleanField
all_questions = Question.objects.annotate(is_complete=Case(
When(
pk__in=profile.completed.values('pk'),
then=Value(True)
),
default=Value(False),
output_field=BooleanField())
)
In your view or your template you can then access question.is_complete which returns true or false.

Django project architecture guidance

I'm working on a project and I'm not sure what my next step is to be or how to best achieve the result that I want.
What I would like to do
Create a quiz-like app where I can add questions and answers, and have users select the right options either through multiple-choice or text input and have the answer validated against the correct question
What I currently have
I have a Django setup of the following
from django.db import models
class Exam(models.Model):
name = models.CharField(max_length=64)
def __str__(self):
return self.name
class Question(models.Model):
question_text = models.CharField(max_length=256)
exam = models.ForeignKey(Exam)
class Answer(models.Model):
text = models.CharField(max_length=128)
question = models.ForeignKey(Question)
def __str__(self):
return self.text
This is what I currently can do
I can use the admin panel to add new questions, answers and exams
I can pass the new data to render them on a template and therefore the website, so if I add a new question I can have it appear on the website
I also know how to create a form that passes data to the server and adds new data to the database without having to use the admin panel
I know how to pass data to a template from the database
What I don't know how to do
I don't know how to display the answers in a way that allows users to interact with them. For example, do I use HTML5 forms, do I have use JavaScript, do I use the Django forms, do I use the Django multipleChoice class?
For starters, if I am able to output the Answers to the Question in a multiple-choice like field that would be a nice start, where the server would return either "Correct" or incorrect based on whether the question is incorrect or not.
What I think might be related to what I need to know
Do I have to use the Django REST framework for this?
Django version: 1.11
If you just show the question and answers as a list and user(s) will answer it (without store the answers and result) ~> just show the list and when user click submit ~> use Ajax to send data (which one you save is as param or json) to server and check the answers in views.py then response the result content to user(s)
If you want to do more than that like:
Multi select answers, save answer as template, save answer in database, save the result, Re-use the answer (1 answer belong to many question)... You need to learn more think like:
Manytomany field, Data flow,... just all the basic thing in Django tutorial

Setting up a weird model in django?

This may be difficult to explain.
I'm a little new to django and the whole idea of models.
Let's say I'm making an article app, where each article has a creator, but other users can edit the article at will. I'm having a little difficult on how to create the models for this.
Firstly,
I extend the user profile with the following:
class UserProfile(models.Model):
#Required field:
user = models.OneToOneField(User)
#Other Fields:
headline = models.CharField()
industry = models.CharField()
article= models.ForeignKey(articleModel.article)
Here is the first place I'm getting confused, do I put the foreignkey field in the user model? My reasoning for it being placed here is because each article can have many editors.
Now here is my article model:
class article(models.Model):
#primary key is already true
creator = models.ForeignKey(userModel.UserProfile)
title = models.CharField()
text = models.TextField()
Over here, I put the ForeignKey field so it would relate back to the creator, because every article has a single creator. (As a side note, I do want to make it so an article can have multiple creators, but I don't know what to do in this scenario).
I'm finding it a bit odd that the UserProfile model is referencing the article model, and the article is referencing it back. Can someone please help me unjumble my brain?
Thank you.
:)
As simple as possible
from django.db.models import *
from django.contrib.admin.models import User
# UserProfile should be provided by django-profiles
class UserProfile(User): # Subclassing user creates an automatic 1-1 called user
headline = CharField()
industry = CharField()
class Article(Model):
# ALWAYS primary key to User, not UserProfile
creator = ForeignKey(User, related_name='articles_created')
contributors = ManyToManyField(User, related_name='articles_edited')
created = DateTime(auto_now_add=True)
modified = DateTimeField(auto_now=True)
title = CharField()
text = TextField()
class Meta:
order = ['created', 'title']
fun stuff:
creator = Article.objects.all()[:1][0].creator.getUserProfile().headline
considder using django-versions if you want to keep track of edits.
class Article(VersionedModel)
EDIT: actually subclasses user
Nothing "weird" here. This is no such a django problem than a database structure problem. You need to read about 1 to 1, 1 to n and n to n relationships between tables.
Do you really need to record all editors of an article ? An article has many editors, and a user can edit many articles, so this is a many to many relationship. Here's how do do it in django.
Perhaps another field in your article model for last editor would provide you with the information you need.
lastEditor = models.ForeignKey(userModel.UserProfile)
If you really want to keep all editors you will need to implement another model which records something like: article_id, editor and edit time (maybe even the article text if you are interested in changes). You could then query this medel based on the current article to obtain a list of all editors.
you could do the same with: article_id and creator to obtain a list of creators of an article (this would replace the article field in your UserProfile class)

What's the best way to replace titles with links to other posts?

I have a post model that looks like this:
class Post(models.Model):
slug = AutoSlugField(populate_from = 'title', unique = True)
title = models.CharField(max_length = 200)
content = models.TextField(blank = True)
is_published = models.BooleanField(default = False)
created_on = models.DateField(auto_now = True)
def get_absolute_url(self):
return reverse('post', args = [self.slug])
When I render the post in a template I would like to replace all mentions of a post title with a link to that post (e.g. if I have a post entitled 'foo' and another posts' content has 'foo' it will be replaced with a link to the post).
For that I have written the following simple template tag (uses django-classy-tags):
class LinkBack(Tag):
options = Options(
Argument('posts', required = True),
'for',
Argument('content', required = True)
)
def render_tag(self, context, posts, content):
output = content
for post in posts:
output = output.replace(post.title, '%s' % (post.get_absolute_url() , post.title))
return output
However I fear that this is going to slow down my website when there are many posts.
Is there a way to optimize that loop?
I could hook it to the pre_save signal but that will only link to existing posts and it feels like I am violating the separation of concerns principle.
What's the best way to do it?
EDIT:
Should I be doing this in a cron job? That way I won't need to deal with the performance issue, however I am still violating SOC here since this is not the data's problem.
Yes, in time this will become even more expensive as the number of posts increase. But, there's a much better approach to have this done. When you consider the use case, you need to know the exact post title for this to work. So, if your already browsing around your site to get the exact title of some post, you should inevitably also come across the post's URL, right? What your doing is creating a clever mechanism that will take raw post titles and magically render them as references to an existing post on your site, but what you should be doing is creating an explicit reference as the post author and defer from any unnecessary post-processing.
Implement support for creating references, be it other posts or some other resources, located on your website or somewhere else. SO, for instance, uses Markdown for creating references via the syntax [Title][#] pointing to the list [#]: http://path.to, but there are many others. I know there are many questions relating to markup languages, some with editors in Django specifically, so I'm sure you can settle on something and have it plugged into your Django instance in no time.

How can I use annotate() to count a subset of related models in Django?

I'm trying to use Django's annotate feature to add the count of a related model to a queryset. However, I don't want a full count of related objects, I only want to count the active ones (i.e., "is_active=True"). I can't figure out how to filter down the count.
The (simplified) relevant models:
class Post(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=80)
body = models.TextField()
class Comment(models.Model):
user = models.ForeignKey(User)
post = models.ForeignKey(Post)
comment_body = models.CharField(max_length=80)
is_active = models.BooleanField(default=True)
In a view, I'm trying to annotate a queryset:
queryset=Post.objects.all().annotate(num_comments=Count('comment', distinct=True))
The above counts all the comments related to a post, whereas I only want to count the "is_active" ones. Google and the Django docs aren't helping me here. Has anyone had and solved this problem?
You just need to filter on is_active before doing the annotation:
Post.objects.filter(comment__is_active=True).annotate(num_comments=Count('comment'))
See the explanation here.
This is how I had to "annotate" the number of active comments on my Post queryset:
Post.objects.extra(select={"num_comments":
"""
SELECT COUNT(myapp_comment.id) FROM myapp_reply
WHERE myapp_comment.is_active='1' AND
myapp_comment.post_id = myapp_post.id
"""
},)
Not pretty, but it works. As I mentioned in a comment above, it wasn't possible to use the built-in aggregation function annotate() for this, since that counted all related comments and I only wanted to count the active related comments.
Daniel's solution didn't work, because it filtered out Posts which had no comments. I don't want to filter out any Posts, just inactive comments.
If anyone has a better solution, I will gladly up-vote and best-answer you!
There is two variants based on what database you are using. If you use MySQL the solution is simple and elegant:
Post.objects.annotate(num_comments=Sum('comment.is_active'))
This works because in database boolean fields are integers and True is 1, False is 0.
But this works ONLY in MySQL and works ONLY for boolean fields. The more general way to do the job which works on all databases and can do more complex checks is using a little SQL inserted a little 'hacky':
Post.objects.annotate(num_comments=Count('comment',
field='CASE WHEN myapp_comment.is_active THEN 1 END'))
I have the same problem in my personal blog, and that was the solution. I write a blog post for that. http://venelin.sytes.net/blog/django/filtrirane-na-agregirash-count-v-django/. It's on bulgarian but my site uses google translation. The translation is not very good but may help to understand why this works.