Ketzu
Ketzu

Reputation: 691

How to model User specific information for Objects simple and efficiently?

As an example, I will use the following code, showing a 'question' similar to stack overflow, and User specific information, e.g., having starred the post.

class Question(models.Model):
    title = models.CharField(max_length=200)
    body = models.CharField(max_length=2000)

class Star(models.Model):
    owner = models.ForeignKey(User, on_delete=models.CASCADE)
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    starred = models.BooleanField()

The goal is now to create a list of all (or the last 20) questions and show a user in this overview which ones they starred.

How to Handle bad Questions? (X)

Why use Fluroid in toothpaste? ( )

Where is Waldo? (X)

This is dependent on the current logged in user.

Creating the list is straight forward, but adding in the boolean seems rather clumsy or inefficient, while in direct SQL, this could be expressed as a left outer join (just fill up non existent values with false).

I tried:

So I wondered if there is a simple way to handle this pattern? Edit: The answer seems to be: No, there is no simple way, but the accepted answer helped me reach a solution (thanks).

Note: I rewrote the question for clarity. Please keep in mind I am a beginner with Django and might have missed some crucial simple thing.

After rephrasing the question I realized it is similar to implementing a like button: Django Like Button

Upvotes: 0

Views: 81

Answers (2)

Bloodmallet
Bloodmallet

Reputation: 424

New answer based on your edited question:

This "solution" is a guess. So please try it out. It might not work exactly as written, or a valid solution might be outside of what I can think of right now.

  1. Collect all questions
  2. Reduce questions to the ones you want to show to user
  3. Create an independent queryset that contains only starred questions of the 2.
  4. Add an artificial attribute to each question of 2. which represents the information you need to put 'X' after each question
  5. Get that changed queryset to the template

views.py:

questions = Question.objects.all()
# limit your results now
# I assume 'questions' going forward was reduced to the number of results you want to show
starred_questions = questions.filter(star_set__owner=request.user, star_set__starred=True)

for question in questions:
    question.starred = question in starred_questions

# get 'questions' to your view now
  1. Loop over questions
  2. Show question
  3. Add 'X' if artificial attribute is present and True

my_template.html:

{% for question in questions %}
    <p>{{ question }}(
        {% if question.starred %}
            X
        {% endif %}
    )</p>
{% endfor %}

I hope this approach will help you reach your goal.


Old answer: Based on

I wanted to show a list of all As and show the associated Bs for a user.

this phrase I guess your view is user specific? Meaning that if user X visits that view, that user sees their own values and if user Y visits that view, they see their own values ones again?

all_b_of_user = request.user.b_set.all().select_related('a')

select_related gets all a data in that same query, to reduce query count, thus response time. doc

If on the other hand you want to show all values of all users on some sort of overview (some user Z is allowed to see all values of X and Y) you'll need to create as many DB queries as you have Users as far as I know.

Upvotes: 1

joshlsullivan
joshlsullivan

Reputation: 1500

from django.views import View
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import Question, Star

class StarredQuestionsView(LoginrequiredMixin, View):
    def get(self, request):
        starred_questions = Star.objects.filter(owner=request.user).filter(starred=True)
        return render(request, "app/list-of-starred-questions.html", {'starred_questions': starred_questions})

This should give you a queryset of all a user's starred questions. In your view, you can do something like this:

{% for question in starred_questions %}
<ul>
<li>{{ question.question.title }}</li>
</ul>
{% endfor %}

Hope this sets you on the right path.

Upvotes: 1

Related Questions