Django dynamic filterset - python

So I want to create a django filters.FilterSet from django-filter module, but I want to dynamically add its attributes. For example, if I wanted to add SubName dynamically:
class UsersInfoFilter(filters.FilterSet):
Name=NumberFilter(lookup_type='gte')
def __new__(self):
self.SubName=NumberFilter(lookup_type='gte')
self.Meta.fields.append('SubName')
class Meta:
model = UsersInfo
fields = ['UserID', 'LanguageID', 'Name']
The problem is that FilterSet is a metaclass that immediately runs once the class has been figured out, so there is nowhere before that point that items can be dynamically added.
I've tried putting a function in as a parameter around filters.FilterSet class UsersInfo(AddObjects(filters.FilterSet)) which returns exactly what is passes, but I cannot reference UsersInfoFilter at that point since it still isn't finished being created.
I also tried making UsersInfoFilter its own base class, and then creating a class RealUsersInfoFilter(UsersInfoFilter, filters.FilterSet) as my actual filter, but then FilterSet just throws warnings about missing attributes named as fields.
There doesn't seem to be any kind of constructor function for classes in python. I'm assuming I have to do some kind of magic with metaclasses, but I've tried every combination I can think of and am at wits end.

You can't change Meta subclass from the __init__ method... there are 2 options to approach your issue...
First one - define "wide" filter on all of the model fields:
class UsersInfoFilter(filters.FilterSet):
class Meta:
model = UsersInfo
It will create default filters for all your model fields.
Second, define dynamic fields:
class UsersInfoFilter(filters.FilterSet):
name = NumberFilter(lookup_type='gte')
def __init__(self):
super(UsersInfoFilter, self).__init__()
base_filters['subname'] = NumberFilter(name='subname', lookup_type='gte')
class Meta:
model = UsersInfo
fields = ['user_id', 'language_id', 'name']
(I do not know if this is something you really want - because despite "dynamic" adding field - it should be declared as static - there are no logic here)
p.s.
why CamelCase on properties and fields? use proper pep-8.

To dynamically choose the fields in the FilterSet, I suggest to create a FilterSet factory like this:
def filterset_factory(model, fields):
meta = type(str('Meta'), (object,), {'model': model, 'fields': fields})
filterset = type(str('%sFilterSet' % model._meta.object_name),
(FilterSet,), {'Meta': meta})
return filterset
And then use it like:
DynamicFilterClass = filterset_factory(model=MyModel, fields=[...])
dynamic_filter = DynamicFilterClass(request.GET, queryset=instances)

Related

How Meta classes works in python?

Origin of question I'm recently working with django and became used to of Meta class in models, Serializers, and Forms.
My Understanding so far I learned that meta classes are used for creating classes.
When one class is defined, Python will go inside the class and collect all attributes and methods and store as dictionary, after that it searches for __metaclass__ attribute. If defined, it will use that class to create the defined class else it will use default object.
Object is default class which is inherited to all classes, and this object class must have __metaclass__ which is type by default.
type class have __new__ and __init__ methods which is used to create classes.
My question
What is the flow of creating a class when we declare Meta class inside definition of class
For example
class Transformer(models.Model):
name = models.CharField(max_length=150, unique=True)
class Meta:
ordering = ('name',)
Where and When this Meta class is used?
Edit 1:
Cleared one thing that metaclasses and django Meta are different.
So Meta is just nested class of Transformer Model Class.
Question: Still my quesition is how this Meta class is used by Model Class?
As put in the comments: Python metaclasses are different from django metaclasses: Django just, for historical reasons, use the same terminology for the inner class where one annotates extra parameters about a class, where the primary members of the outer class are meant to correspond to fields in a model or form.
A Python metaclass, on the other hand, are what you are describing in your example, though you have checked some Python 2 documentation. In current Python, the metaclass is determined by passing the keyword argument "metaclas=" in the declaration of a new class, where the base classes go:
class MyClass(Base1, Base2, metaclass=MyMeta):
...
As far as I know it, the Django behavior had origin in which early versions of Django actually used a custom (Python) metaclass to annotate some of the parameters now used in the nested Meta - and in doing so, it took a shortcut of defining the metaclass inline inside the class body: instead of assigning the __metaclass__ name to an externally defined metaclass, as the usual for normal use, it would just define the class inplace: from the point of view of the language runtime, it would find the name __metaclass__ bound to a valid metaclass and use that to build the class.
Later versions, even in Python 2, modified this approach - the inner class was no longer the actual "metaclass" of the Model or Form (as the previous approach was clearly overkill).
Model Meta is basically the inner class of your model class. Model Meta is basically used to change the behavior of your model fields like changing order options,verbose_name_plural, and a lot of other options. It’s completely optional to add a Meta class to your model.
example:
class Category (models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, unique=True)
def __str__(self):
return self.name
class Meta:
verbose_name_plural= 'Categories'

Share data between base class and Meta class

I have the following django model:
class Article(models.Model):
filename = models.CharField(max_length=255)
collection = models.ForeignKey(Collection, on_delete=models.CASCADE)
keys = ['filename', 'collection']
class Meta:
constraints = [
models.UniqueConstraint(
fields=['filename', 'collection'],
name='article_key')
]
As you can see I've defined the same list ['filename', 'collection'] in both the base class and the Meta class. I would like to define it once. I can't define it in Meta because then I get 'Meta got an unrecognised attribute 'keys'. So I must define it in the base class and access it from Meta. I don't know how to share data between the two. I've tried doing:
self.keys
in Meta but that gives 'self is not defined'. I've also tried with just 'keys' but that's also not defined. Any tips? Thanks.
EDIT
Thank you to Willem for pointing out that I can define keys in Meta if I just call it '_keys'. If I do this, however, the question is then how do I access _keys from the base class? I've tried 'meta._keys' and 'Meta._keys'. Both not defined.
EDIT 2
For clarity, the reason that I want 'keys' defined in the base class is that I will (a) be accessing it from properties on the base class, and (b) want to be able to access it from the outside.
You can declare it before the class, then reference it from both the model class and it' Meta:
# making it a tuple since you probably don't want
# it to be mutable
_ARTICLE_KEYS = ('filename', 'collection')
class Article(models.Model):
# making it an implementation attribute since you
# probably don't want to be writeable
# (hint: provide a read-only property for access)
_keys = _ARTICLE_KEYS
class Meta:
constraints = [
models.UniqueConstraint(
fields=_ARTICLE_KEYS,
name='article_key')
]
But this is still ugly IMHO and very probably unecessary - the model's methods should be able to access those values thru self._meta.contraints[0].fields or something similar (don't have models with such constraints at hand right now so I can check how this is actually transformed by the models's metaclass but inspecting self._meta in your django shell should give you the answer).
The methods of a nested class cannot directly access the instance attributes of the outer class.
So, in your case, If you won't use the keys list in the Article class, just defined it once in the Meta class. Otherwise, you need to defined twice!

Django - generic ModelAdmin definition

This is a newbie question.
I have multiple models that I would like to show in admin view. I would like to show all model's fields when viewing a list of records for a model.
I'd like to define a generic ModelAdmin subclass which will be able to list out all fields for a model. But not sure how to pass the class objects to it. admin.py looks like this
class ModelFieldsAdmin(admin.ModelAdmin):
def __init__(self, classObj):
self.classObj = classObj
self.list_display = [field.name for field in classObj._meta.get_fields()]
admin.site.register(Candy, ModelFieldsAdmin)
admin.site.register(Bagel, ModelFieldsAdmin)
How can I pass the classObj (which would be one of the models Candy or Bagel) , and also ensure that the get_list_display() picks up the value from self.list_display ?
This isn't the right approach. You shouldn't override __init__ for an admin class.
Rather, define get_list_display and access self.model:
def get_list_display(request):
return [field.name for field in self.model._meta.get_fields()]

Django - Model field define unicode

As we can define the __unicode__ representation of a model,
Is there a way to define the same for a model field ? (or is it a bad idea ?)
You can add your own methods. For example, when you use choices for a field, django automatically creates a get_FIELD_display method for the FIELD.
class Something(models.Model):
name = models.CharField(max_length=25)
def get_name_uppercase(self):
return self.name.upper()
then when you have
something = Something.get(id=1)
you can access it via
something.get_name_uppercase()

Django: programatic fields in inlines

Expanding from this question: Can "list_display" in a Django ModelAdmin display attributes of ForeignKey fields?, could it be possible to do something like this:
class MyModelInline(admin.StackedInline):
model = MyModel
extra = 1
fields = ('my_field',)
def my_field(self, obj):
return obj.one_to_one_link.my_field
If something like this were possible, it would solve most of my current Django problems, but the code above does not work: Django (rightly) complains that my_field is not present in the form.
You can do that, but you must also add my_field to your MyModelInline class's readonly_fields attribute.
fields = ('my_field',)
readonly_fields = ('my_field',)
From the docs:
The fields option, unlike list_display, may only contain names of fields on the model or the form specified by form. It may contain callables only if they are listed in readonly_fields.
If you need the field to be editable, you should be able to do that with a custom form but it takes more work to process it.

Categories