Setting relationship with chosen model class in Django Admin interface - python

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.

Related

Django Model choices based off another model

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

Customize related field in Django model specifying foreign keys to relate through

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.

Two foreign key in the same model from two different model

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.

Django model select choice, populated with fields from other model instances

Suppose I have a model Car that has a field brand and a model owner that has two field: name and car_brand, whereas the latter should be one of the Car instances brands. I need to have a form where the user is presented with a text field name and a drop down select choice populated with all the brand name of Car instances. How would I achieve that?
Here is the code I am starting with. Feel free to correct. Thanks.
models.py
from django.db import models
class Car(models.Model):
brand = models.CharField(max_length=20)
class Owner(models.Model):
name = models.CharField(max_length=20)
car_brand = models.ForeignKey(Car)
forms.py
from django.forms import ModelForm, TextInput, Select
from app.models import Owner
class OwnerForm(ModelForm):
class Meta():
model = Owner
fields = ("name", "car_brand")
widgets = {
"name" : TextInput(attrs={"class" : "name"}),
"car_brand" : Select(attrs={"class" : "car_brand"}),
}
You could probably just define a __unicode__ method on your Car model and I think it should work fine. As Daniel mentioned, the form might be getting confused since you've overridden the widgets. I could be wrong, but I thought django automatically rendered attributes on form elements that can be used for styling purposes. Maybe you don't need to override the widgets. If not, you can specify the form field explicitly:
class OwnerForm(ModelForm):
car_brand = forms.ModelChoiceField(
queryset=Car.objects.all(),
widget=Select(attrs={'class': 'car_brand'}),
)
class Meta:
model = Owner
fields = ('name', 'car_brand')
widgets = {
'name': TextInput(attrs={'class': 'name'})
}
As a side note, is there any reason you don't have a CarBrand model and a foreign key field relating the Car model to it? That would be a more normalized approach to modeling your data.

Django forms with Foreign keys

I have scenario in which a user can have multiple books. I can create two different models for user and books and relate them using foreign keys (or one-to-many will be right way ?).
I have created a django forms for User model but when i do like this {{form.as_p}} in templates only user model fields is shown not books field.
I want that with user fields my books model filed also displayed (like book names field more then once because he can have multiple books) , Please let me know if it is possible using django forms/models or I have to user simple html forms with jquery and then save data in models.
Thanks
EDIT:
my models :
class Product(models.Model):
categories = models.CharField(max_length=5, choices = settings.CATEGORIES)
name = models.CharField(max_length=100)
description = models.TextField()
currency = models.CharField(max_length=5, choices = settings.CURRENCY)
status = models.BooleanField(default=True)
def __unicode__(self):
return self.name
class Prices(models.Model):
products = models.ForeignKey(Product)
prices = models.IntegerField()
def __unicode__(self):
return self.id
if you are creating a form for Prices, try putting this in your model form:
products = forms.ModelMultipleChoiceField(queryset=Product.objects.all())
I think you should add required fields in meta class such as
class ThreadForm(ModelForm):
class Meta:
model = Thread
fields = ('Books', 'User')
Please understand the work flow to use foreign keys in model form here.

Categories