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.
Related
Problem:
How to add relationship from chosen model instance to any other Django model dynamically via Django Admin interface?
Description:
I want to create Categories via Django Admin interface. Each Category has multiple Choices assigned to it. Choice(s) from given Category may be assigned only to objects of another specific Django class (model). Let's present a pseudocode example:
class Category(models.Model):
category_name = models.CharField()
class Choice(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="choices")
choice_name = models.CharField()
class ModelWithChoosableFields(models.Model):
possible_categories = ... # objects of class Category
selected_choices = ... # objects of class Choice
class Meta:
abstract = True
class Book(ModelWithChoosableFields):
...
class Animal(ModelWithChoosableFields):
...
Category with category_name = 'Genre' has three possible
Choices: choice_name = 'biography', choice_name = 'thriller'
and choice_name = 'poetry'.
Category with category_name = 'Animal type' has two possible
Choices: choice_name = 'mammal' and choice_name = 'reptile'.
Class Book may have one of the Choices from Category
category_name = 'Genre' assigned. However, Choices related to
category_name = 'Animal type' cannot be assigned to class Book.
Analogically, class Animal can only have Choices related to
category_name = 'Animal type' assigned to it.
In other words, in Admin panel for instance of class Book I want to have a way of selecting Choice objects from Categories appropriate for Book class.
The reason I want to do this is so that user using Django Admin interface can add dynamically possible Categories to chosen models (e.g. add Category category_name = "Conservation status" choosable for class Animal), add more Choices to Categories if needed (e.g. add another choice_name = 'fish' to category_name = 'Animal type'. This way it is very flexible for end admin user, no need to change anything in code.
I tried achieving it with Generic Relations - however, it wasn't successful, because AFAIK generic relation ties given object (e.g. Category) to instance of object of any other class, not generally to any other class (so, when using Generic Relations, for Category I would have to specify relationship with given Book object instance, not with Book class in general).
I wonder if such action is even feasible - I searched a lot and couldn't find anything. Maybe there is a simpler way? Thank you in advance!
With ContentTypes you can relate model instances to entire model classes, no overwriting necessary to achieve your goal.
Heres how to do it:
In your Category model define a many-to-many relationship to ContentType. This way, in your Category model-forms you will be able to choose which models this category applies to and you will be able to filter Choices based on whether their category contains a particular model. Use the limit_choices_to parameter of the ManyToManyField to restrict the ContentType choices to those with the correct app_label and of course exclude the Choice and Category models.
From the Book/Animal/Etc. models add many-to-many relationships to the Choice model and use the limit_choices_to parameter to limit the Choices to only those with a category which is related to the respective model.
Your models should then look somewhat like this:
from django.db import models
def get_ContentTypes():
appQ = models.Q(app_label='YourAppName') #change value of app_label to match your app's name
modelIsCatQ = models.Q(model='category')
modelIsChoice = models.Q(model='choice')
return appQ & ~modelIsCatQ & ~modelIsChoice
class Category(models.Model):
category_name = models.CharField(max_length=128)
asigned_models = models.ManyToManyField(ContentType,limit_choices_to=get_ContentTypes)
class Choice(models.Model):
choice_name = models.CharField(max_length=128)
category = models.ForeignKey(Category,on_delete=models.Model)
class Animal(models.Model):
choices = models.ManyToManyField(Choice,limit_choices_to=models.Q(category_assigned_models__model__startswith='animal'))
class Book(models.Model):
choices = models.ManyToManyField(Choice,limit_choices_to=models.Q(category_assigned_models__model__startswith='book'))
Aaand Voila. There you have it:
When creating/editing a category, you choose which models it should apply to
When creating/editing a Book/Animal/etc. you can only see the relevant choices.
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.
Sorry if this is confusing, I'm still a bit green with Django. So basically I have two models and I want a selection from one to have all the choices from another model. So basically:
class Show(models.Model):
venue = models.CharField(max_length=100, choices = VENUE NAME)
class Venues(models.Model):
Name = models.CharField(max_length=100)
Essentially I want the venue to have a list of the venue names that were input into that model. Is this possible?
In your case you should use many-to-one ForeignKey
It give you access to Venues object from your Show object and it simple to add this to your model.
class Show(models.Model):
venue = models.ForeignKey('Venues', on_delete=models.CASCADE)
class Venues(models.Model):
name = models.CharField(max_length=100)
To get your choices you can use:
Venues.objects.all()
And then the only thing you need is add object or ID to your Show object and save.
Choices are good too but not in this case. For example when you need some const and give user choices like this:
class Show(models.Model):
VENUES_CHOICES = (
(RESTAURANT, 'restaurant'),
(PUB, 'pub'),
)
venues = models.IntegerField(choices=VENUES_CHOICES, default=RESTAURANT)
Its great to use it in order status in my opinion.
add a str def like this in the Venues model will do
def __str__ (self):
return self.name
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'm trying to create a model for Django that looks like this:
class Device(Model):
UDID = CharField(length=64, primary_key=True)
# more irrelevant stuff
class DeviceProperty(Model):
device = ForeignKey(Device)
name = CharField(length=255)
value = CharField(length=255)
readOnly = BooleanField()
But then, for data-integrity reasons, a single device shouldn't have two properties with the same name. So I would need to make the device and name fields of DeviceProperty jointly unique.
A simple way to achieve this would be by having a composite primary key on the two fields, but this raises a few issues, and more importantly isn't supported by Django.
I haven't found anything about this in the Django documentation. Have I missed anything?
unique_together is what you want.
class DeviceProperty(Model):
…
class Meta:
unique_together = ['device', 'name']