django admin inline many to many custom fields - python

Hi I am trying to customize my inlines in django admin.
Here are my models:
class Row(models.Model):
name = models.CharField(max_length=255)
class Table(models.Model):
rows = models.ManyToManyField(Row, blank=True)
name = models.CharField(max_length=255)
def __unicode__(self):
return self.name
and my admin:
class RowInline(admin.TabularInline):
model = Table.rows.through
fields = ['name']
class TableAdmin(admin.ModelAdmin):
inlines = [
RowInline,
]
exclude = ('rows',)
However I get this error
ImproperlyConfigured at /admin/table_app/table/1/
'RowInline.fields' refers to field 'name' that is missing from the
form.
How is that possible ?

class RowInline(admin.TabularInline):
model = Table.rows.through
fields = ['name']
This presents a problem because Table.rows.through represents an intermediate model. If you would like to understand this better have a look at your database. You'll see an intermediate table which references this model. It is probably named something like apname_table_rows. This intermeditate model does not contain the field, name. It just has two foreign key fields: table and row. (And it has an id field.)
If you need the name it can be referenced as a readonly field through the rows relation.
class RowInline(admin.TabularInline):
model = Table.rows.through
fields = ['row_name']
readonly_fields = ['row_name']
def row_name(self, instance):
return instance.row.name
row_name.short_description = 'row name'
class TableAdmin(admin.ModelAdmin):
inlines = [
RowInline,
]
exclude = ('rows',)

Django can not display it as you expected. Because there is an intermediary join table which joins your tables. In your example:
admin.py:
class RowInline(admin.TabularInline):
model = Table.rows.through # You are not addressing directly Row table but intermediary table
fields = ['name']
As the above note, model in RowInline addressing following table in your database, not Your Row table and model
table: your-app-name_table_row
--------------------------------
id | int not null
table_id | int
row_id | int
You can think it like there is an imaginary table in your model that joins the two tables.
class Table_Row(Model):
table = ForeignKey(Table)
row = ForeignKey(Row)
So if you edit your Inline as following
class RowInline(admin.TabularInline):
model = Table.rows.through # You are not addressing directly Row table but intermediary table
fields = ['row', 'table']
you will not see any error or exception. Because your model in RowInline addresses an intermediary table and that table do have those fields. Django can virtualize the imaginary table Table_Row up to here and can handle this.
But we can use relations in admin, with using __. If your code do have a ForeignKey relation instead of ManyToManyField relation, then following be valid in your admin
class Row(models.Model):
name = models.CharField(max_length=255)
class Table(models.Model):
rows = models.ForeignKey(Row, blank=True)
name = models.CharField(max_length=255)
def __unicode__(self):
return self.name
and your admin:
class RowInline(admin.TabularInline):
model = Table
fields = ['rows__name']
Because you will have real Models and djnago can evaluate __ relation on them
But if you try that in your structure:
class Row(models.Model):
name = models.CharField(max_length=255)
class Table(models.Model):
rows = models.ManyToManyField(Row, blank=True)
name = models.CharField(max_length=255)
def __unicode__(self):
return self.name
and your admin:
class RowInline(admin.TabularInline):
model = Table.rows.through
fields = ['row__name']
it will raise Exception! Because you do not have real table in your model, and django can not evaluate __ relations on virtual models it designs on top of its head.
Conclusion:
In your Inlines addressing ManyToMany relations, you are dealing with an imaginary intermediary model and you can not use fields orexclude attributes on that because your imaginary model do not have those fields and django can not handle relations ober that imaginary table. Following will be acceptable
class RowInline(admin.TabularInline):
model = Table.rows.through
# No fields or exclude declarations in here
and django will display combo boxes for your virtual intermediary table options and add a fancy green + sign to add new records, but you will can not have inline fields to add new records directly to your database within the same single page. Djnago can not handle this on a single page.
You can try creating real intermediary table and show it using through, but thats totally a longer job and I do not test it to see its results.
Update: There is also the reason why django do not let something like that. Consider following:
Table | Table_Row | Row
-----------+-----------+---------
Start 5 | |
Step 1 5 | | 1
Step 2 5 | 5-1 | 1
At the beginning, You have a table with no related Rows, you want to add a row to the table... For joining a row with a table, you must first create a row so you execute step 1. After You do create your row, you can create Table_Row record to join these two. So in contains more than a single database insertion. Django crew may avoid such usage since it contains multiple inserts and operation is related more tables.
But this is just an assumption on the reason of the behavior.

In your admin.py try this
class RowInline(admin.TabularInline):
model = Table.rows.through
list_display = ('name',)
class TableAdmin(admin.ModelAdmin):
inlines = [
RowInline,
]
readonly_fields = ('rows',)

Related

how can we update one to one relationship models db data from django?

i am new to django and i created onetoOneField relationship model with inbuilt User model of django but i cant figure out how can i update that model class table value.
My model
class category(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
TYPES = [
("BASIC","BASIC"),
("PLUS","PLUS"),
("PRO","PRO")
]
User_Type = models.CharField(max_length=40,choices=TYPES,default="BASIC")
Product_Limit = models.IntegerField(default=0)
Amount_Paid = models.IntegerField(default=0)
Tried these 2 cases but getting some error
#key = category.objects.update_or_create(id=request.user.id,User_Type= request.user.category.User_Type,Product_Limit=2,Amount_Paid=request.user.category.Amount_Paid)
user = request.user.category.update_or_create(Product_Limit=10)
Try this:
category = Category.objects.filter(user=request.user).update_or_create(
user=user, Product_Limit=10
)
This will make two SQL queries:
First, Django will make a SELECT ... FROM category WHERE user_id = .... As category and user has a one to one relationship, this should return 0 or 1 row.
If the first query has no record, Django will make an INSERT INTO category query with user_id=request.user.id and Product_Limit=10, the other fields following the default values.
If the first query has a record, Django will make a UPDATE category SET user_id = ..., Product_Limit = ... WHERE user_id = ... query to update the Product_Limit field.

Custom Form Field Naming Within Loop

I'm trying to specify field names within a loop on a form, so that the fields do not overwrite themselves. Right now I'm using this in my form:
class TicketForm(forms.ModelForm):
class Meta:
model = Ticket
exclude = ['products']
class ProductForm(forms.Form):
product_id = forms.IntegerField()
count = forms.IntegerField()
class TicketProductForm(forms.ModelForm):
class Meta:
model = Ticket
exclude = []
for Product in Product.objects.all():
product_id = forms.IntegerField(initial=Product.product_id)
count = forms.IntegerField(initial=0)
The for loop within TicketProductForm currently only displays one set of fields, as product_id and count overwrite the fields over and over, only displaying for the last entry in the set of Product.
Is there a way to customize field names so that I can name them, preferably with a way I can set a specifier to allow easier lookup later?

My django model joining wrong fields when there are multiple related fields with join

My product model has two different relationship with category model:
One to One
One to Many through ProductShadowCategory table.
Now the situation is when I tried to fetch using the second relationship, I am getting the result from my first relationship.
For example this is what I am trying to print:
Category.objects.get(slug="root").shadow_products.all()
but it converts to following sql :
print(Category.objects.get(slug="root").shadow_products.all().query)
SELECT `product_management_product`.`id`, `product_management_product`.`slug`, `product_management_product`.`category_id`, `product_management_product`.`brand_id` FROM `product_management_product` WHERE `product_management_product`.`category_id` = 720
My models looks like following:
class Category(SlugableModel):
#...
shadow_products = models.ManyToManyField("product_management.Product", through="product_management.ProductShadowCategory")
class Product(SlugableModel):
#...
category = models.ForeignKey(Category,on_delete=models.CASCADE, related_name="products", validators=[leaf_category])
class ProductShadowCategory(MyModel):
category = models.ForeignKey(Category,on_delete=models.CASCADE)
product = models.ForeignKey(Product,on_delete=models.CASCADE)
class Meta:
unique_together = ('category', 'product')

Get fields from a third model based on the foreign key of a second model

I have three models, one of which has an ID from each of the others to join them. The issue I am trying to solve is how I can get the values From table one into table three using table two to join them in the serializer, or if that is even the approach I should be taking to begin with.
model
class ModelOne(models.Model):
color = models.CharField()
size = models.CharField()
class ModelTwo(models.Model):
One_id = models.ForeignKey(ModelOne)
Three_id = models.CharField()
class ModelThree(models.Model):
example serializer
class ModelThree Serializer
model = ModelThree
fields = ('color', 'size')
def get_color(self, obj):
???
def get_size(self, obj):
???
my db structure
ModelOne
-------------------------------------
id color size
ModelTwo
-------------------------------------
id ModelOneID ModelThreeID
ModelThree
-------------------------------------
id
If I understand your question correctly, you have two models ModelOne and ModelThree which are joined by another model ModelTwo
class ModelOne(models.Model):
color = models.CharField(max_length=200)
size = models.CharField(max_length=200)
class ModelTwo(models.Model):
one = models.ForeignKey(ModelOne)
three = models.ForeignKey('ModelThree')
class ModelThree(models.Model):
some_field = models.CharField(max_length=200)
# this looks like a ManyToMany relationship between ModelThree and ModelOne
# where ModelTwo is the intermediate model
# you may want to check the docs https://docs.djangoproject.com/en/1.11/topics/db/models/#many-to-many-relationships
and you want to be able to query values from ModelOne into ModelThree.
If you have an instance of ModelThree m3, then you can get all related ModelOne instances by querying the other side of the foreignkey relation like so:
# ex: m3 = ModelThree.objects.first()
model_twos = m3.modeltwo_set.all()
for two in model_twos:
print two.one.color, two.one.size
I don't get it yet but you have: list id of Model 1 right?
~> You want to have model 3 (query by model2)
# The flow: models2.objects.filter(model1_id__in = md1_ids)
# And you want to get model3 ? ~> need ids to query
# ~> Query 1:
md3_ids = models2.objects.filter(model1_id__in = md1_ids).values_list("model3_id", flat="true")
md3 = Models3.objects.filter(pk__in md3_ids)
Is this it? I don't know what you want...

Django - filtering based on filed from other model

I am working on some Django app and I met with a problem that I don't know how to solve.
I have to models:
class A(models.Model):
STATE_NEW = 1
STATE_DELIVERED = 2
STATE_LOST = 3
STATE_CHOICES (
(STATE_NEW, 'New order'),
(STATE_DELIVERED, 'Delivered'),
(STATE_LOST, 'Lost'),
)
order_id = models.IntegerField()
status = models.IntegerField(u'Status', default=NEW_STATE, choices=STATE_CHOICES)
class B(models.Model):
# some_fields
order = models.ForeignKey(A)
I have some fields in model B. What I want to achieve is to add filter to admin site for model B that allows filtering on "status" (field from model A). Is it possible to achieve it without defining new field in model B?
Nothing comes to my mind because I don't want to keep "status" field with all possible STATE_CHOICES in model B (because "status" is not a field of model B).
Could you please help me? :)
You can do this using list_filter
list_filter = ('order__status', )
from docs:
Field names in list_filter can also span relations using the __ lookup

Categories