The following sample database stores posts of news and relevant information for each piece of news. I'm interested in retrieving the topics associated with each news. The problem is, they are stored in different tables with complex relationships.
Each news is assigned with a newsid in the table NewsFeed:
class NewsFeed(models.Model):
newsid= models.OneToOneField('NewsSub',
on_delete=models.CASCADE, db_column='newsid',
primary_key=True)
def __str__(self):
return str(self.newsid)
An one-to-one relationship is defined between the field newsid in the class NewsFeed and the model NewsSub:
class NewsSub(models.Model):
newsid = models.BigIntegerField(primary_key=True)
In another class NewsTopic, a foreignKey relationship is defined between the field newsid with the model NewsSub:
class NewsTopic(models.Model):
newsid = models.ForeignKey(NewsSub, on_delete=models.DO_NOTHING,
db_column='newsid')
topicid = models.ForeignKey(NewsLabel, on_delete=models.DO_NOTHING,
db_column='topicid', related_name = 'topic')
In the NewsTopic db table, each newsid may correspond to more than one topicid. Finally, the field topicid of the class NewsTopic is related to the model NewsLabel:
class NewsLabel(models.Model):
topicid = models.BigIntegerField(primary_key=True)
topiclabel = models.CharField(max_length=100)
def __str__(self):
return self.topiclabel
In the NewsLabel db table, each toicid corresponds to a unique topiclabel.
My goal is to retrieve the topiclabel(s) associated with each NewsFeed object, by querying the newsid. Suppose result represents one such object, I'm wondering is it possible to do something like result.newsid.topicid.topiclabel?
Thanks and sorry for the long descriptions!!
#EvelynZ
You can use the prefech_related to fetch the values of the related object, please check the https://docs.djangoproject.com/en/2.1/ref/models/querysets/#prefetch-related
Related
I'm making one of my first django apps with sqlite database. I have some models like for example:
class Connection(models.Model):
routeID = models.ForeignKey(Route, on_delete=models.CASCADE)
activityStatus = models.BooleanField()
car = models.ForeignKey(Car, on_delete=models.CASCADE)
class Route(models.Model):
name = models.CharField(max_length=20)
and forms
class RouteForm(ModelForm):
class Meta:
model = Route
fields = ['name']
class ConnectionForm(ModelForm):
class Meta:
model = Connection
fields = ['routeID', 'activityStatus', 'car']
And in my website, in the url for adding new Connection, I have cascade list containing RouteIDs. And I'd like it to contain RouteName, not ID, so it would be easier to choose. How should I change my ConnectionForm, so I could still use foreign key to Route table, but see RouteName instead of RouteID?
For now it's looking like this, but I'd love to have list of RouteNames, while still adding to Connection table good foreign key, RouteID
Update the Route Model's __str__ method:
class Route(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
Because the __str__() method is called whenever you call str() on an object. Django uses str(obj) in a number of places like in Modelform. By default it returns id or pk that is why you were seeing ids in model form. So by overriding it with name, you will see the names appear in choice field. Please see the documentation for more details on this.
Looking at graphene_django, I see they have a bunch of resolvers picking up django model fields mapping them to graphene types.
I have a subclass of JSONField I'd also like to be picked up.
:
# models
class Recipe(models.Model):
name = models.CharField(max_length=100)
instructions = models.TextField()
ingredients = models.ManyToManyField(
Ingredient, related_name='recipes'
)
custom_field = JSONFieldSubclass(....)
# schema
class RecipeType(DjangoObjectType):
class Meta:
model = Recipe
custom_field = ???
I know I could write a separate field and resolver pair for a Query, but I'd prefer it to be available as part of the schema for that model.
What I realize I could do:
class RecipeQuery:
custom_field = graphene.JSONString(id=graphene.ID(required=True))
def resolve_custom_field(self, info, **kwargs):
id = kwargs.get('id')
instance = get_item_by_id(id)
return instance.custom_field.to_json()
But -- this means a separate round trip, to get the id then get the custom_field for that item, right?
Is there a way I could have it seen as part of the RecipeType schema?
Ok, I can get it working by using:
# schema
class RecipeType(DjangoObjectType):
class Meta:
model = Recipe
custom_field = graphene.JSONString(resolver=lambda my_obj, resolve_obj: my_obj.custom_field.to_json())
(the custom_field has a to_json method)
I figured it out without deeply figuring out what is happening in this map between graphene types and the django model field types.
It's based on this:
https://docs.graphene-python.org/en/latest/types/objecttypes/#resolvers
Same function name, but parameterized differently.
So I have two models:
class UserData(models.Model):
""" Holds basic user data. """
id = models.IntegerField(primary_key=True, editable=False) # id is taken from data.
class ConsumptionTimePoint(models.Model):
""" Individual consumption time points with a One-to-Many relationship with UserData """
user_data = models.ForeignKey(UserData, on_delete=models.CASCADE)
And when I try and test them by creating them both, and their relationship in a test:
def test_basic_model_creation(self):
user_data_object = UserData.objects.create(id=1)
user_data_object.save()
consumption_time_point_object = ConsumptionTimePoint.objects.create(user_data=user_data_object)
consumption_time_point_object.save()
self.assertIsNotNone(consumption_time_point_object.user_data)
self.assertEquals(1, len(user_data_object.consumption_time_point_set.all()))
I get the following error:
AttributeError: 'UserData' object has no attribute 'consumption_time_point_set'
But from my understanding that's the correct way to get the set. Have I misnamed something? Or is this a testing issue?
To get the related queryset the class name is lowercased and _set is appended. Try consumptiontimepoint_set
You can also set the reverse relation name manually by using the related_name parameter.
In my Django project I have a model for products that look like this:
class Manufacturer(models.Model):
name = models.CharField(max_length=100)
class Product(models.Model):
manufacturer = models.ForeignKey('Manufacturer')
# .favorite_set: ManyToOne relation coming from the
# 'Favorite' class (shown a couple of lines below)
My site's User(s) can mark some products as Favorite. To provide this functionality, I have a Django model that looks like this:
class Favorite(models.Model):
user = models.ForeignKey(User)
product = models.ForeignKey('Product')
class Meta:
unique_together = ('user', 'product',)
In that model, the .product ForeignKey creates a reverse relation in the Product model called favorite_set. That's all good and useful: When I get an HTTP request from a user to retrieve products, I can easily figure out whether it's been favorited by a particular user or not by doing this:
product = Product.objects.get(id='whatever_id')
is_favorited = bool(product.favorite_set.filter(user=self.user).count() == 1)
# or probably:
# is_favorited = product.favorite_set.filter(user=self.user).exists()
#
Now, I have another model that is heavily denormalized (SQL denormalized, that is) that I want to use for fast text searches.
This model "pretends" to be a Product, but includes data found through the "regular" Product's FK relationships into the model itself. Something like this:
class ProductSearch(models.Model):
product = models.OneToOneField('Product',
on_delete=models.CASCADE,
related_name='searcher')
product_name = models.CharField(max_length=100)
manufacturer_name = models.CharField(max_length=100)
This class has its own id field (since it's a Django model) and, as you can see above, it is going to have a OneToOne relationship to the products (one of this ProductSearch entries is linked to one and only one Product)
Thanks to this model, if I want to search products whose manufacturer is "Ford" (for example), I don't need to join the Product table with the Manufacturer's table. I can do the lookup directly in ProductSearch and save a few milliseconds.
Since the ProductSearch is intended to be compatible with a Product, I'm also trying to model the favorite_set that occurs "naturally" in my Product class into this ProductSearch model.
And that's where the difficulties arise: I don't know how to do that :-)
I ideally would have something like:
class ProductSearch(models.Model):
product = models.OneToOneField('Product',
on_delete=models.CASCADE,
related_name='searcher')
manufacturer_name = models.CharField(max_length=100)
#
# Couldn't find anything to do the following:
product_favorite_set = models.ManyToOneField('Favorite',
through_fields=('product',))
But I haven't been able to do that.
I have tried to "abuse" the ManyToManyField like this:
class ProductSearch(BaseModel):
product = models.OneToOneField('Product',
on_delete=models.CASCADE,
related_name='searcher')
product_name = models.CharField(max_length=100)
manufacturer_name = models.CharField(max_length=100)
product_favorite_set = models.ManyToManyField('Favorite', related_name='+',
through='Favorite',
through_fields=['product']
)
But that produces an error on System Check:
api.Favorite: (fields.E336) The model is used as an intermediate model
by 'api.ProductSearch.product_favorite_set', but it
does not have a foreign key to 'ProductSearch' or 'Favorite'.
api.ProductSearch.product_favorite_set: (fields.E339) 'Favorite.product'
is not a foreign key to 'ProductSearch'.
I imagine I could make the product_favorite_set a Python #property, and then do a custom query in it like:
class ProductSearch(BaseModel):
# ....
#property
def product_favorite_set(self):
return Favorite.objects.filter(product=self.product)
But I would like to know if I can do this using "pure" Django tools (only if out of curiosity)
Any help would be greatly appreciated. Thank you in advance.
I need to put two Foreign Keys in a model class from two different models. I would like to relate the third model class to the first and the second.
I've tried something like this:
class A (models.Model)
id_A = models.IntergerField (primary_key=True)
#...
class B (models.Model)
id_B = models.IntergerField (primary_key=True)
#...
class C (models.Model)
id_A = models.ForeignKey(A)
id_B = models.ForeignKey(B)
Reading the docs I understand that is not possible to have MultipleColumnPrimaryKeys... but I Didn't receive any error from django with this models.py
Is it possible? Is there a smarter way?
Thank you!
You are doing it well, if you were relating id_A and id_B to a same model, django would give you an error, in this case just put related_name attribute in the second field.
Django didn't pop up error because you were doing it right. It's totally reasonable that there are multiple foreign keys in one model, just like class C.
In class C, as long as id_A and id_B is the single column primary keys of their own model, it will perfectly work out.
"MultipleColumnPrimaryKeys" you mentioned is a different thing. It means that for a specific table in database, there are multiple columns together to be the table's primary key, which is not supported in Django.
You can use a many-to-many relationship that automatically creates that intermediate model for you:
from django.db import models
class Publication(models.Model):
title = models.CharField(max_length=30)
class Article(models.Model):
headline = models.CharField(max_length=100)
publications = models.ManyToManyField(Publication, related_name='articles')
With this you can do both:
publication.articles.all() # Gives you the articles of the current publication instance
article.publications.all() # Gives you the publications of the current article instance
Check out docs for many to many
If you need to use any additional fields in that intermediate model, you can tell django which is the through model like this:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
Check out docs for extra fields on many to many
Also, if you are going for a standard integer auto-increment primary key, django already generates one for you so you don't need to define id_A and id_B.