Selecting related objects in django - python

I have following problem:
My application have 2 models:
1)
class ActiveList(models.Model):
user = models.ForeignKey(User, unique=True)
updatedOn = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.user.username
'''
GameClaim class, to store game requests.
'''
class GameClaim(models.Model):
me = models.ForeignKey(ActiveList, related_name='gameclaim_me')
opponent = models.ForeignKey(ActiveList, related_name='gameclaim_opponent')
In my view I took all ActiveList objects all = ActiveList.objects.all() and passed it to the template
In template I am looping through every item in the ActiveList, and create an xml file which is used on my client application.
the question is:
How can I query the info about the claims which one user (e.g. test, part of ActiveList), made to the user who is under loop
user2 e.g is taken like this
{% for item in activeList %}
{% endfor %}
user 2 is an item in this case

What you are looking at doing belongs more properly in the view than the template. I think you want something like:
claimer = User.objects.get(name='test')
claimed_opponents = User.objects.filter(gameclaim_opponent__me__user=claimer)
Then you can pass those into your template, and operate on them directly.
You might also look at rethinking how your tables relate to one another. I think claims should probably go directly between users, and whether a given user is active should be external to the relationship. I would think a user should be able to claim a game with an inactive user, even if they have to wait for the user to reactivate before that game can begin.

I'm not sure I entirely understand your question, but I think the information you're looking for might be here: http://docs.djangoproject.com/en/dev/topics/db/queries/
Perhaps you could clarify the question if you don't find an answer there?

Related

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:
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.
Collect all questions
Reduce questions to the ones you want to show to user
Create an independent queryset that contains only starred questions of the 2.
Add an artificial attribute to each question of 2. which represents the information you need to put 'X' after each question
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
Loop over questions
Show question
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.

Referencing user object inside model method in Django

I have the following in my model:
class Genre(models.Model):
name = models.CharField(max_length=100)
def my_latest_song(self):
song = Song.objects.filter(genre_id=self.id, author_id=XXXXXXXXX).order_by('-date')
return song[0];
class Song(models.Model):
name = models.CharField(max_length=100)
genre = models.ForeignKey(Genre, on_delete=models.CASCADE, null=True)
date = models.DateField()
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
So with the my_latest_song method I want to obtain for each genre what the latest song is for the user currently logged in. Note: I am already making sure that users MUST be logged in to see this, do not worry about this additional validation.
The question is: how can I pass the user id of the user that is currently logged in onto this model method? Where the XXXXXXXXXX is listed I have tried:
author_id=request.user.id
author_id=user.id
author=request.user
author=user
And many similar options. But I'm new to Django and just not sure about how to properly reference this. Nothing is working yet.
Please note: the issue is that I am constantly (in many different views) displaying the latest song for each genre. So that's why I am thinking it makes sense in the model. It's not stored in a database, it's simply retrieved within the model. Please let me know if this is not appropriate.
For instance, on many pages I am showing a list of genres and for each genre the latest song uploaded by that user:
Genre | Last Song
----------------------
Pop | Song 1
Rock | Song 33
Classic | Song 32
Something like this maybe?
...
def my_latest_song(self, user_id):
song = Song.objects.filter(genre_id=self.id, author=User.objects.get(pk=user_id)).order_by('date')
return song[0];
...
You will not be able to use request.user.id or any other request value in that method, as the model does not actually use requests. That usually is done by a form.
You should not be making this query in your model. It is much better to make the query in views.py whenever you need the information.
Below is, if I understand correctly, what you would need to do to make this query in the view.
def relevant_view(request,genreID_from_url):
# ... #
song = Song.objects.filter(author=request.user).filter(genre=genreID_from_url).order_by('-date')[:1]
# ... #
A couple of points:
I think you would need to pass the genre you are querying for in the URL. Here is a good tutorial for that: http://django-book.readthedocs.io/en/latest/chapter03.html#your-third-view-dynamic-urls . You could also do it using a form - it depends on the circumstances in which the query needs to be made.
You need to order by '-date' and not 'date' so that you get the most recent song at the start of the query.
The [ ] at the end is Django's syntax for limiting a queryset. The above limits it to only the first item of the queryset, as you are attempting to do.
The page in the Django docs for queries is really helpful and clear: https://docs.djangoproject.com/en/2.0/topics/db/queries/

Django: filter by a property defined on a model, in a DRY way?

I'm working in Django 1.10. I have a model like this:
class MyEvent(models.Model):
class = models.ForeignKey(Class)
starts = models.DateTimeField()
url = models.URLField(blank=True, null=True)
def in_past(self):
return self.starts.date() < datetime.date.today()
def is_interesting(self):
return self.url or not self.in_past()
In a couple of different places in my app, I want to show only events that are "interesting" as defined above, i.e. have a URL or aren't in the past. I want to display headers in the template if any such events exist.
I don't seem to be able to do this in my views:
events = MyEvent.objects.filter(is_interesting=False)
in the view. In the template, I can filter individual events by this property, but I can't tell if there are any such events:
{% if events.is_interesting.any %}Interesting events{% endif %}
always returns false.
Is there any way I can get a list of "interesting" events in my views or templates, in a DRY way?
You are unable to filter like that because is_interesting isn't a model field. It's a function in your model, which can only be executed in the python side but cannot be executed in the DB side. You need to rewrite your query as follows.
events = MyEvent.objects.exclude(url=None).exclude(url='').filter(starts__lte=date.today())
note that you have defined your url as
url = models.URLField(blank=True, null=True)
Which forces you to check for both blank and null.
Once you got the query working you can create a custom queryset manager and and define a function that modifies the queryset to include those filters and keep things dry
If you want to do an OR condition you need Q
from django.db.models import Q
events = MyEvent.objects.exclude(Q(Q(url=None) | Q(url='')) & Q((starts__lte=date.today())
I can't quite figure out the exact mix of OR and AND you want to use. Hope you will be able to figure it out using this guide line and the refferenced document. As already mentioned, using both null=True and blank=True makes your task that much harder.
You can define such methods on your custom managers and/or QuerySets:
https://docs.djangoproject.com/en/1.11/topics/db/managers/#adding-extra-manager-methods

Django Add list generated from the text of one field to many to many field

Having a bit of trouble trying to bulk add a list of items to a many to many field and though having tried various things have no clue on how to approach this. I've looked at the Django documentation and cant seem to find what I'm looking for.
Here is the code for my models:
class Subject(models.Model):
noun = models.CharField(max_length=30, null=True, blank=True)
class Knowledge(models.Model):
item_text = models.TextField()
item_subjects = models.ManyToManyField(Subject, null=True, blank=True)
def add_subjects(sender, instance, *args, **kwargs):
if instance.item_info:
item_subjects = classifier.predict_subjects(instance.item_info)
if item_subjects:
....
post_save.connect(add_subjects, sender=Knowledge)
The list is being generated by the classifer.predict_subjects function.
I have tried using the m2m_changed connector and the pre_save and post_save connect. I'm not even sure the many to many field is the right option would it be better to do make a foreign key relationship.
in place of the '...' I have tried this but it doesn't create the relationship between and only saves the last one.
for sub in item_subjects:
subject = Subject(id=instance.id, noun=sub)
subject.save()
I've also tried
instance.item_subjects = item_subjects
and a load more things that I can't really remember, I don't really think I'm in the right ballpark to be honest. Any suggestions?
edit:
ok, so I have got it adding all of the list items but still haven't managed to link these items to the many to many field.
for sub in item_subjects:
subject = Subject.objects.get_or_create(noun=sub)
edit 2:
So doing pretty much exactly the same thing outside of the loop in the Django shell seems to be working and saves the entry but it doesn't inside the function.
>>> k[0].item_subjects.all()
<QuerySet []>
>>> d, b = Subject.objects.get_or_create(noun="cats")
<Subject: cats>
>>> k[0].item_subjects.add(d)
>>> k[0].item_subjects.all()
<QuerySet [<Subject: cats>]>
edit 3
So I took what Robert suggested and it works in the shell just like above just not when using it in the admin interface. The print statements in my code show the array item being updated but it just dosen't persist. I read around and this seems to be a problem to do with the admin form clearing items before saving.
def sub_related_changed(sender, instance, *args, **kwargs):
print instance.item_subjects.all()
if instance.item_info:
item_subjects = classifier.predict_subjects(instance.item_info)
if item_subjects:
for sub in item_subjects:
subject, created = Subject.objects.get_or_create(noun=sub)
instance.item_subjects.add(subject)
print instance.item_subjects.all()
post_save.connect(sub_related_changed, sender=Knowledge)
I have tried using the function as m2m_changed signal as follows:
m2m_changed.connect(model_saved, sender=Knowledge.item_subjects.through)
But this either generates a recursive loop or doesn't fire.
Once you have the subject objects (as you have in your edit), you can add them with
for sub in item_subjects:
subject, created = Subject.objects.get_or_create(noun=sub)
instance.item_subjects.add(subject)
The "item_subjects" attribute is a way of managing the related items. The through relationships are created via the "add" method.
Once you've done this, you can do things like instance.item_subjects.filter(noun='foo') or instance.item_subjects.all().delete() and so on
Documentation Reference: https://docs.djangoproject.com/en/1.11/topics/db/examples/many_to_many/
EDIT
Ahh I didn't realize that this was taking place in the Django Admin. I think you're right that that's the issue. Upon save, the admin calls two methods: The first is model_save() which calls the model's save() method (where I assume this code lives). The second method it calls is "save_related" which first clears out ManyToMany relationships and then saves them based on the submitted form data. In your case, there is no valid form data because you're creating the objeccts on save.
If you put the relevant parts of this code into the save_related() method of the admin, the changes should persist.
I can be more specific about where it should go if you'll post both your < app >/models.py and your < app >/admin.py files.
Reference from another SO question:
Issue with ManyToMany Relationships not updating inmediatly after save

Generalized Model in Django

My first question helped me a LOT, so I figured I'd go ahead and ask a second one.
My current (practice) project is to create a generalized model. Here's what I've written so far:
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Block(models.Model):
title = models.CharField(man_length=50)
#Make sure child classes have content defined!
#content = models.BooleanField(default=False)
owner = models.ForeignKey('User')
access = models.ManytoManyField('User', null=True)
links = models.ManytoManyField('self', null=True)
class Meta:
abstract = True
def __unicode__(self):
return self.title
class Text(Block)
content = models.TextField(max_length=100)
class URL(Block)
content = models.URLField()
class Email(Block)
content = models.EmailField()
Please note I haven't actually tested this yet - I don't think my logic on this so far is going to work.
A few goals:
1) owner there points to the creator of the block. I think that should work.
2) access should point to Users (other than the owner) who can edit the block. I think this should work too. (with proper views of course)
3) links should allow linking between blocks - so I can retrieve a block and any related blocks (and so on up if need be)
4) content should of course be the content of the block(I have the three sample simple data types here), which ideally can be any number of things (implemented here via the abstract base class).
5) For the classes Email, URL, and Text, I'd like to be able to write a (generalized) view which from one url input, returns an appropriate block of any of the three types. I think this may require an autoincrementing field which guarantees a unique value between the three models. Honestly, I'm not sure how to do this at all.
6) Of course ideally this should be readily extensible if at all possible.
I'm of course very unsure if this is the best way to go about achieving what I'm trying to do. Any suggestions, help, tips, tricks, or bad jokes accepted!
Thank you very much for your help!
EDIT: relating to number 5, I have a rough view (with a syntax error) that I think might do the trick:
def getblock(request, block, no):
data = get_object_or_404(%s,%s) % (block,no)
return render_to_response('single.html',{'data':data})
Thoughts?
Your code seems correct to me. I also worked on a generic class for my project and it was similar to this one. One remark, when you get a block, you should do some type checking to retrieve the class data so, I recommend you to add a field to store its type. e.g:
class Block(models.Model):
title = models.CharField(max_length=50)
type = models.IntegerField()# 1 for text, 2 for email and 3 for url
#Make sure child classes have content defined!
#content = models.BooleanField(default=False)
owner = models.ForeignKey('User')
access = models.ManytoManyField('User', null=True)
links = models.ManytoManyField('self', null=True)
Then in your view:
def getblock(request, block, no):
if block.type == 1:
data = get_object_or_404(Block,id=no).text
elif block.type == 2:
data = get_object_or_404(Block,id=no).email
elif block.type == 3:
data = get_object_or_404(Block,id=no).url
return render_to_response('single.html',{'data':data})
You can access the subclasses with lower case representation of that child class. I think that will work nicely. Also I don't get the dictionary type arguments you wrote and that gave a syntax error when I executed. Anyway, I also corrected some of the syntax errors, too.

Categories

Resources