简体   繁体   中英

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, eg, 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:

  • select related can not be used, as not all questions have starred entries
  • Reversing the relationship in the template does not work due not being able to use a parameter on method calls
  • Looping the list and adding the secondary information with a .get()

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

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM