How to exclude fields from form created via PolymorphicChildModelAdmin - python

Playing a little around with Polymorphic and additional plugins I'm wondering how I can prevent some of the base class fields from being showed inside form for child admin interface. Having this adminy.py for my child class:
from django.contrib import admin
from .models import *
from partsmanagement.models import Part
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin
admin.site.register(Book)
class BookAdmin(PolymorphicChildModelAdmin):
base_model = Part
and this admin.py for the base model:
# -*- coding: utf-8 -*-
from django.contrib import admin
from .models import *
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin
from bookcollection.models import Book
from bookcollection.admin import BookAdmin
admin.site.register(Part)
class PartAdmin(PolymorphicParentModelAdmin):
base_model = 'Part'
child_models = (
(Book, BookAdmin),
)
Now the form inside admin shows all fileds of base and child class. I tried to add exclude = list() for child class but this didn't work (no change).

Filtering for classes (equivalent to python's isinstance() ):
>>> ModelA.objects.instance_of(ModelB)
.
[ <ModelB: id 2, field1 (CharField), field2 (CharField)>,
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
In general, including or excluding parts of the inheritance tree:
ModelA.objects.instance_of(ModelB [, ModelC ...])
ModelA.objects.not_instance_of(ModelB [, ModelC ...])
You can also use this feature in Q-objects (with the same result as above):
>>> ModelA.objects.filter( Q(instance_of=ModelB) )
Polymorphic filtering (for fields in derived classes)
For example, cherrypicking objects from multiple derived classes anywhere in the inheritance tree, using Q objects (with the syntax: exact model name + three _ + field name):
>>> ModelA.objects.filter( Q(ModelB___field2 = 'B2') | Q(ModelC___field3 = 'C3') )
.
[ <ModelB: id 2, field1 (CharField), field2 (CharField)>,
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
Combining Querysets
Querysets could now be regarded as object containers that allow the aggregation of different object types, very similar to python lists - as long as the objects are accessed through the manager of a common base class:
>>> Base.objects.instance_of(ModelX) | Base.objects.instance_of(ModelY)
.
[ <ModelX: id 1, field_x (CharField)>,
<ModelY: id 2, field_y (CharField)> ]

Related

Django abstract model with indices, constraints and permissions: attributes are not inherited by sub classes

I am using Django 3.2
I have the following models:
app1
class IsPinnable(models.Model):
is_pinned = models.BooleanField(default=False)
pin_after_expiry_day_count = models.DurationField(default=0)
class Meta:
abstract = True
indexes = [
models.Index(fields=['is_pinned' ]),
]
class IsModeratable(models.Model):
approved = models.BooleanField(default=False)
target_count_trigger = models.PositiveSmallIntegerField()
positive_pass_count = models.PositiveSmallIntegerField(default=0)
negative_pass_count = models.PositiveSmallIntegerField(default=0)
# Other fields and methods ...
def save(self, *args, **kwargs):
# TODO: Sanity check on pass_count and trigger sizes
# ...
super().save(*args, **kwargs)
class Meta:
abstract = True
permissions = (
("moderate_item", "can moderate item"),
("reset_moderation", "can (re)moderate a moderated item"),
)
indexes = [
models.Index(fields=['approved' ]),
]
class MyComponent(IsPinnable, IsModeratable):
# some fields and methods ...
class Meta(IsPinnable.Meta, IsModeratable.Meta):
abstract = True
# other stuff ...
app2
from app1.models import MyComponent
class Foo(MyComponent):
# some fields and methods ...
class Meta(MyComponent.Meta):
abstract = False
Now, I know that abstract model classes are not created in the database - so I was initially expecting Django to throw an exception when I attempted to makemigrations - to my surprise, I was able to makemigrations && migrate.
However, when I inspected the database (via psql), I found that although table app2_foo had all the fields described in it's parent class, the indixes were not being carried up from the parent classes as the the documentation would seem to suggest.
What am I missing?, and how do I get the indices, constrains and permissions defined in parent classes to propagate to sub classes?
This is just normal class inheritance behaviour, take this snippet for instance:
class A:
attr = [1]
class B:
attr = [2]
class C(A, B):
pass
print(C.attr) # Outputs: [1]
This is because A is before B in the Method Resolution Order. If suppose C defined attr = [3] then the output would have been [3].
You can do something like this in the child class if you need to override those attributes:
class MyComponent(IsPinnable, IsModeratable):
# some fields and methods ...
class Meta(IsPinnable.Meta, IsModeratable.Meta):
abstract = True
indexes = [...] + IsPinnable.Meta.indexes + IsModeratable.Meta.indexes # explicitly assign the indexes here
Note: Although What you want can be made possible if Django decides to put that logic in the meta class (this is different from
Meta here) of the Meta, but I believe that would then require much
work on their end to merge such attributes and would be backwards
incompatible and honestly would be a weird feature, what if one
doesn't want those indexes?

Django TrigramSimilarity returns errors with Full Text Search on GIN index

Hello I'm trying to make search on Django with postgresql/FTS on a GIN indexed column but get a weird error. This error does ot appear on CharField but only on a SearchVectorField:
the Postgresql Database has the pg_trgm extension installed (within a Django migrations)
This is the Objects :
class Language(models.Model):
code = models.CharField(primary_key=True, max_length=5, verbose_name=_("code"))
label = models.CharField(max_length=50, verbose_name=_("label"))
search = SearchVectorField(null=True)
objects = Language_Manager()
class Meta:
verbose_name = _("language")
verbose_name_plural = _("languages")
indexes = [
GinIndex(fields=["search"], name="search_index",),
]
def save(self):
fields = [x for x in dir(self) if x[:5] == "label"]
labels = [getattr(self, field) for field in fields if getattr(self, field)]
self.search = self.search + " ".join(labels)
super().save()
and here is my query :
from core.models import Language as LanguageCode
from django.contrib.postgres.search import TrigramSimilarity
LanguageCode.objects.all().annotate(sim=TrigramSimilarity("search", "english"))
it returns an error message :
LINE 1: ...e_language"."label_ko", "core_language"."search", SIMILARITY...
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
if I use a standard CharField instead of a GIN indexed SearchVectorField, the error does not occurs :
from core.models import Language as LanguageCode
from django.contrib.postgres.search import TrigramSimilarity
LanguageCode.objects.all().annotate(sim=TrigramSimilarity("label", "english"))
<MultilingualQuerySet [<Language: Language object (fr)>, <Language: Language object (en)>, >><Language: Language object (it)>, ...
am-I doing something wrong ?
I'm also using Django-model-translation with the label column of this object:
from modeltranslation.translator import translator, TranslationOptions
from core.models import Language
class LanguageTranslationOptions(TranslationOptions):
fields = ("label",)
translator.register(Language, LanguageTranslationOptions)

TypeError: 'DeferredAttribute' object is not iterable

In my models.py I have the following class:
class AvailabilityTypes():
STUDYING = 'STUDYING'
WORKING = 'WORKING'
SEARCHING = 'SEARCHING'
FREELANCER = 'FREELANCER'
types = (
(STUDYING, 'Estudando'),
(WORKING, 'Trabalhando'),
(SEARCHING, 'Procurando por emprego'),
(FREELANCER, 'Freelancer')
)
def get_types(self):
return self.types.all()
And I want to show that options in a Django Form. In my forms.py file I have the following piece of code:
from django import forms
from .models import AvailabilityTypes
[...]
availability = forms.CharField(
widget=forms.ChoiceField(
choices=(AvailabilityTypes.types)
)
)
But I get the error TypeError: 'DeferredAttribute' object is not iterable. What am I doing wrong? Also, if I try using:
availability = forms.CharField(
widget=forms.ChoiceField(
choices=(AvailabilityTypes.get_types())
)
)
I get the error TypeError: get_types() missing 1 required positional argument: 'self'.
I'm new to Django and Python, I could use some light. Thank you.
Sol 1. Fix your current code
First fix your method get_types():
class AvailabilityTypes():
...
# Above this Same as your code
def get_types(self):
return self.types # You need to return just types, it is a tuple, it doesn't has an attribute all(). In Django we usually use all() in querysets.
Now fix the form:
from django import forms
from .models import AvailabilityTypes
at_obj = AvailabilityTypes() # Create an object of class AvailabilityTypes
[...] # Guessing form class starts here
availability = forms.CharField(
widget=forms.ChoiceField(
# choices=AvailabilityTypes.get_types() # You can't call a class's method on that class, you call it on that class's object
choices=(at_obj.get_types()) # Call the method on object of the class not class itself
)
)
Sol 2. Don't create unnecessary class
In models.py there is no need to create a class to hold types. you can do this:
...
# All your imports go above this
# These four variables are pointless in my opinion, but I will leave them be
STUDYING = 'STUDYING'
WORKING = 'WORKING'
SEARCHING = 'SEARCHING'
FREELANCER = 'FREELANCER'
# types is an constant so it should follow uppercase naming style
TYPES = (
(STUDYING, 'Estudando'),
(WORKING, 'Trabalhando'),
(SEARCHING, 'Procurando por emprego'),
(FREELANCER, 'Freelancer')
)
# Create your models Here
...
Now in your forms.py:
...
from .models import TYPES # Import the tuple from models
[...]
availability = forms.CharField(
widget=forms.ChoiceField(choices=TYPES) # Use the imported tuple directly here
)
Sol 3. Use model form(simplest and easiest)
In your models:
from django.db import models
# types is an constant so it should follow uppercase naming style
TYPES = (
('STUDYING', 'Estudando', ),
('WORKING', 'Trabalhando', ),
('SEARCHING', 'Procurando por emprego', ),
('FREELANCER', 'Freelancer', ), # Unlike other languages your last element can have a trailing comma too, its optional but still do that. I has some advantages which I am not gonna explain here
)
# Create your models Here
class MyModel(models.Model):
availability = models.CharField(max_length=63, choices=TYPES)
# Other fields of your model
...
Now in your forms.py:
from django import forms
from .models import MyModel
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('availability', ) # add other fields of your models too in the tuple
And that's it. You are done, use your forms in views. Django will take care of showing right choices, validating them, showing relevent error messages and saving the valid data in database.

Django inheritance and subclass attributes

With a configuration such as this:
class A (models.Model):
common_attribute = models.IntegerField()
class B (A):
subclass_attribute = models.IntegerField()
class C (models.Model)
a = ForeignKey(A)
... if an instance of C contains an instance of B, Django seems to treat this as an instance of A and I can't access c.a.subclass_attribute. Is there a way around this? I don't want to use abstract inheritance because of the difficulties with ForeignKeys - I want C to support any subclass of A.
You can circumvent the difficulties with ForeignKey by using django-polymorphic.
Django Polymorphic allows you to query the base class objects but retrieves the child class instances:
>>> Project.objects.create(topic="Department Party")
>>> ArtProject.objects.create(topic="Painting with Tim", artist="T. Turner")
>>> ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
>>> Project.objects.all()
[ <Project: id 1, topic "Department Party">,
<ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">,
<ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter"> ]
To use django polymorphic you only need to declare your models with Polymorphic Model as base class:
from django.db import models
from polymorphic import PolymorphicModel
class ModelA(PolymorphicModel):
field1 = models.CharField(max_length=10)
class ModelB(ModelA):
field2 = models.CharField(max_length=10)
class ModelC(ModelB):
field3 = models.CharField(max_length=10)
Foreign keys will also return the child class instances, which I think is exactly what you want.
# The model holding the relation may be any kind of model, polymorphic or not
class RelatingModel(models.Model):
many2many = models.ManyToManyField('ModelA') # ManyToMany relation to a polymorphic model
>>> o=RelatingModel.objects.create()
>>> o.many2many.add(ModelA.objects.get(id=1))
>>> o.many2many.add(ModelB.objects.get(id=2))
>>> o.many2many.add(ModelC.objects.get(id=3))
>>> o.many2many.all()
[ <ModelA: id 1, field1 (CharField)>,
<ModelB: id 2, field1 (CharField), field2 (CharField)>,
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
Take into account that these queries will be slightly less performant.

django admin inline many to many custom fields

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',)

Categories