I have a model defined with a generic foreign key like this:
class Status(models.Model):
request_type = models.ForeignKey(ContentType)
request_id = models.PositiveIntegerField()
request = GenericForeignKey('request_type', 'request_id')
When I grab the relations with a normal queryset it works fine.
statuses = Status.objects.all()
for status in statuses:
print status.request.name
This prints all the names normally.
However I am using Django eztables which relies on grabbing the values like
statuses = Status.objects.all().values('request__name')
However when I try this I get:
FieldError: Cannot resolve keyword 'request' into field. Choices are: request_id, request_type, request_type_id
Is there a way to achieve this in Django?
I figured it out.
You must define your object with names like this:
class Status(BaseRequestStatus):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
... more data
Then in the object you would like to filter on you must create a reverse generic relationship:
class Request(models.Model):
statuses = GenericRelation(Status, related_query_name='request')
... other data
Then finally you can do fun stuff like
statuses = Status.objects.all().values('request__name')
or
Status.objects.filter(request__name__contains="stack overflow")
According to the docs for the contenttypes framework:
Due to the way GenericForeignKey is implemented, you cannot use such fields directly with filters (filter() and exclude(), for example) via the database API.
Since values() is a part of this API and is listed just two sections below filter()/exclude() in the docs for Aggregations and other QuerySet clauses, my guess is that is why your approach fails.
I would recommend this instead:
status_list = Status.objects.all()
statuses = [{'request__name': status.request.name} for status in status_list]
Of course, the dict keys in the list comprehension can be whatever you'd like. I left them as 'request__name', since that is what you would've gotten if your approach had worked properly.
Related
I'm trying to write a query that when an artist id is searched, that artist's info and all of their pieces are the result, basically a join query between Artist and ArtistPiece
#models.py
class Artist(models.Model):
name = models.CharField(max_length=100)
artist = models.CharField(max_length=150)
created_by = models.ForeignKey(User)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class ArtistPiece(models.Model):
artist = models.ForeignKey(Artist, on_delete = models.CASCADE)
piece_name = models.CharField(max_length=50)
details = models.CharField(max_length=100)
#views.py
from .models import Artist, ArtistPiece
class EditArtistViewSet(viewsets.ViewSet):
def edit(self,request,artist_id):
artist = Artist.objects.select_related().get(pk=artist_id)
data = model_to_dict(artist)
return render(
request, 'ArtistInfo/edit.html', {
'editing': 'true',
'edit_data': json.dumps(data),
})
def submit_edit(self,request):
return render(request, 'ArtistInfo/edit.html')
The above works, but it doesn't select any of the data from the ArtistPiece model, despite the primary key. Then after reading this post about using select_related I realized that rather than looking up the artist, I'd have to look up ArtistPiece by the foreign_key, and use select_related to find the info about the artist.
I then tried this
def edit(self, request, artist_id):
artist = ArtistPiece.objects.get()
data = model_to_dict(artist)
Which resulted in the following error get() returned more than one ArtistPiece -- it returned 5!. Searching, I found this post which states that I need to use filter rather than get. I then tried this
def edit(self, request, artist_id):
artist = ArtistPiece.objects.select_related('artist').filter(artist=artist_id)
data = model_to_dict(artist)
Which gave the following error 'QuerySet' object has no attribute '_meta'. I tried searching for this error and found this which basically suggests to use get, which seems to go against everything else I found.
To summarize, I just want to select an artist and their pieces but I can't seem to figure out how.
Edit - I tried a few things and made some progress, but it's still not working correctly
Just to be clear I'm trying to write a query that will look up an artist by their id, and the result will be the artist's info and the artist's pieces. I can query and retrieve the Artist model based on an artist_id or the ArtistPiece model, but not one query that will return both Artist and all of the ArtistPiece entries associated to that artist.
Adding related_name='pieces'to my model as #bignose has suggested, I then tried artist = Artist.objects.select_related('pieces').filter(pk=artist_id) but that still gave the no attribute.... error above. I then changed data = model_to_dict(artist) to data = [model_to_dict(a) for a in artist] and it worked, however the data was only the artist and nothing from the ArtistPiece model.
Instead of querying the Artist model I instead tried to query the ArtistPiece model as such: artist = ArtistPiece.objects.select_related('artist').filter(artist=artist_id), but then only the artist's pieces were returned and no info from the Artist model.
model_to_dict accepts single model object, but your are passing into that the object of QuerySet type (which is quite similar to list). To get the list of dicts all the objects, you need to explicitly call model_to_dict on each model object like:
data = [model_to_dict(a) for a in artist]
Better way is to use QuerySet.values() as:
data = artist.values()
As per the Queryset.Values() document:
Returns a QuerySet that returns dictionaries, rather than model instances, when used as an iterable.
Each of those dictionaries represents an object, with the keys corresponding to the attribute names of model objects.
Also take a look at Serializer class. I think it will be useful for you.
ArtistPiece.objects.filter(artist=artist_id), this will return a queryset.
If you want a list you can do ArtistPiece.objects.filter(artist=artist_id).values_list('id', flat=True)
https://docs.djangoproject.com/en/1.10/ref/models/querysets/#values-list
The ForeignKey field type automatically also sets an attribute on the foreign model; in your case, Artist.artistpieces_set.
favourite_artist = Artist.objects.get(pk="foo") # However you get an Artist instance.
favourite_artist.artistpieces_set # Is a QuerySet of all the ArtistPiece instances that reference this instance.
# Because it's a QuerySet, you can iterate it, filter it, etc.
for piece in favourite_artist.artistpieces_set.all():
put_in_exhibition(piece)
Thus, when you get an Artist instance, it automatically has an attribute that is a QueryManager for the ArtistPiece instances referencing that Artist.
This is good because it means the foreign key relationship is defined in one place, but it is also accessible from the other end.
See the documentation for “Following relationships “backwards”” for how to use these features.
The Django docs describe how to query reverse m2m fields:
https://docs.djangoproject.com/en/1.8/topics/db/queries/#many-to-many-relationships
According to this answer we should use related_name as the first argument in the query.
But I'm having trouble making this work. (I'm using Django 1.8.5). Here are my example models:
class Nlpneutralfeature(models.Model):
neutral_feature = models.CharField(u'found word', max_length=255, default='')
class Userproject(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="project", verbose_name=("owner"))
monitoring_words = models.ManyToManyField(Nlpneutralfeature, null=True, blank=True, related_name="monitoringwords")
name = models.CharField(u'Название проекта', unique=True, max_length=255)
So, to get all Nlpneutralfeature rows that belong to Userproject where Userproject.name == '48', I need to do:
Nlpneutralfeature.objects.filter(userproject__monitoringwords__name='48')
But this does not work. The error I get is: Cannot resolve keyword *'userproject'* into field.
So, Django cannot understand that 'userproject' is lowercased model name Userproject.
Now, this is working:
Nlpneutralfeature.objects.filter(monitoringwords__name='48')
How does Django know that monitoringwords is related_name? No error is strange to me, sicne there is no monitoringwords field in the Nlpneutralfeature model!
Note the phrasing in the Django docs:
The name to user for the relation from the related object back to this one. It's also the default value for related_query_name (the name to use for the reverse filter name from the target model).
In your example, the "target model" is Nlpneutralfeature. When you set related_name = 'monitoringwords' on the ManyToManyField Userproject.monitoringwords, it tells Django to use that related_name to refer from Nlpneutralfeature model objects back to the corresponding Userproject model objects.
Correspondingly, if you had declared Userproject.monitoringwords with related_name = 'foo', the way to query for all Nlpneautralfeatures belonging to project 48 would be: Nlpneautralfeature.objects.filter(foo__name='48').
the related_name here is so you can get your Userproject models related to an Nlpneutralfeature model.
eg:
nf = Nlpneutralfeature.objects.get(neutral_feature="XXX")
# From here you can get all Userprojects related to this nf like this:
userProjects = nf.monitoringwords.all()
if you didn't declared a related_name then you had to do it like this:
nf = Nlpneutralfeature.objects.get(neutral_feature="XXX")
# From here you can get all Userprojects related to this nf like this:
userProjects = nf.Userproject_set.all()
i think you understaned this property by inverse.. in my opinion i thing you should declare it here like this: related_name="user_projects"
in summary, related_name is to use when you want to get the current models(where you declare your M2M relationship) from the related model.
I have two models from which I'd like to get one result on a foreign key.
# Master List:
class Block(models.Model):
lesson = models.ForeignKey('Lesson')
name = models.CharField(max_length=100)
# Contains revisions, i.e. updates:
class BlockContent(models.Model):
block = models.ForeignKey('Block')
content = models.TextField()
type = models.IntegerField(default=1)
revision = models.IntegerField(default=0)
latest = models.BooleanField(default=True)
Ideally for template purposes, I'd like to be able to call block.content or block.type for instance, but this doesn't seem to be easily possible.
Getting the BlockContent by foreign key seems to be a pain- despite there only being a one-to-one result in this specific view, it requires .all() and a lots of iteration.
Stuff I've tried:
additional = BlockContent.objects.get(block=block.id)
block.blockcontent_set.add(additional)
What you have is a one to many relationship in which Block can have many BlockContent objects.
The way to get content from a block is by using reverse relation, I don't see a problem with that:
content = block.blockcontent_set.all()[0].content
to get the content of the first blockcontent in the queryset.
Of course order_by and all other queryset Django goodnes can be used here.
You could also define a related_name to:
class BlockContent(models.Model):
block = models.ForeignKey('Block', related_name=blockcontent)
and use it like:
content = block.blockcontent.all()[0].content
I've got a model with a recursive relationship to itself:
class Tweet(models.Model):
text = models.CharField(max_length=140)
original = models.ForeignKey("self", null=True, blank=True)
And a serializer that renders the original Tweet inline:
class TweetSerializer(serializers.ModelSerializer):
class Meta:
model = Tweet
fields = ('id', 'text', 'original', 'original_id')
original_id = serializers.IntegerField(source='original_id', required=False)
def to_native(self, obj):
ret = super(TweetSerializer, self).to_native(obj)
del ret['original_id']
return ret
TweetSerializer.base_fields['original'] = TweetSerializer(source='original', read_only=True)
As you can see I've also got an original_id field that is removed in to_native. The purpose of original_id is to allow me to set the original_id of a new tweet, rather than having to supply a full blown Tweed object to the original field. You could say that I'm using it as a write only field.
This seems a bit clunky though. Is there a better way to do it?
OK, two points:
Have you tried using PrimaryKeyRelatedField for your original_id? It would seem to target your use-case specifically. Combined with the depth option it may give you everything you need.
You can switch serializers (e.g. based on request method) by overriding get_serializer_class() on your view. Not sure if you'll get the exact behaviour you want here though.
I realize that select_related only works on foreign key and one-to-one relationships, but it seems there should be a simple, select_related-like way to join over many-to-many relations that are unique together provided all but one of the unique_together parameters is given.
class User(models.Model):
article_access_set = models.ManyToManyField(Article,
through='UserArticleAccess', related_name='user_access_set')
# User Information ...
class Article(models.Model):
# Article Information ...
class UserArticleAccess(models.Model):
user = models.ForeignKey(User)
article = models.ForeignKey(Article)
# UserArticleAccess Information: flags, liked, last_access_time, ...
class Meta:
unique_together = ('user', 'article')
I'm looking for a magical method:
qs = Article.objects.all().magical_select_related(select={
'user_access_set': {'user': request.user}})
print qs[0].user_access_set
# <UserArticleAccess ...>
print qs[1].user_access_set # No Access
# None
Or maybe:
qs = Article.objects.all().magical_select_related(select = {
'user_access_set': {'user': request.user}},
as = {'user_access_set': 'user_access'})
print qs[0].user_access
# <UserArticleAccess ...>
print qs[1].user_access # No Access
# None
Is there any way to do this? (Or a reason that this shouldn't be implemented in this way or a similar way?)
With Django 1.10 you can use django.db.models.fields.related.ForeignObject class (it's not a public API).
See the conversation about this issue in https://groups.google.com/forum/#!topic/django-users/pGGHKb4Y8ZY
You can see examples in tests/foreign_object/tests.py of the following commit:
https://github.com/django/django/commit/80dac8c33e7f6f22577e4346f44e4c5ee89b648c
Please, look at this post Django : select_related with ManyToManyField.
prefetch_related() can join data but in python.