Inherit and modify a `Meta` class - python

So I have a base ItemTable, and then a number of Tables that inherit from it. I don't seem to be able to modify the Meta class. I tried just including the meta class normally and it didn't work, then I found this bug report and implemented it below. It fails silently: the tables render only with the columns from the parent meta class.
class ItemTable(tables.Table):
class Meta:
model = Item
attrs = {"class":"paleblue"}
fields = ('name', 'primary_tech', 'primary_biz', 'backup_tech', 'backup_biz')
class ApplicationTable(ItemTable):
def __init__(self, *args, **kwargs):
super(ApplicationTable, self).__init__(*args, **kwargs)
class Meta(ItemTable.Meta):
model = Application
fields += ('jira_bucket_name',)
EDIT: Code amended as shown. I now get a NameError that fields is not defined.

Try:
class ApplicationTable(ItemTable):
class Meta:
model = Application
fields = ItemTable.Meta.fields + ('jira_bucket_name',)
You'll have the same problems extending Meta in a table, as you will in a normal Django model.

You didnt add , (comma) to one-element tuple. Try to change this line Meta.attrs['fields'] += ('jira_bucket_name') in ApplicationTable to:
Meta.attrs['fields'] += ('jira_bucket_name',)
if it didnt help try to create Meta class outsite model class definition:
class ItemTableMeta:
model = Item
attrs = {"class":"paleblue"}
fields = ('name', 'primary_tech', 'primary_biz', 'backup_tech', 'backup_biz')
class ApplicationTableMeta(ItemTableMeta):
model = Application
fields = ItemTableMeta.fields + ('jira_bucket_name',)
class ItemTable(tables.Table):
#...
Meta = ItemTableMeta
class ApplicationTable(ItemTable):
#...
Meta = ApplicationTableMeta

You may need to take this up with the django-tables author. This is not a problem with standard Django.

Related

Parametrize a Python class

Is there any way to parametrize a class in Python? The parametrized class may look something like this:
class FIELDSerializer:
FIELD = serializers.CharField(source='get_FIELD_display', required=False)
class Meta:
model = None
fields = {FIELD}
Which would need to create the following three classes:
class NameSerializer:
name = serializers.CharField(source='get_name_display', required=False)
class Meta:
model = None
fields = {'name'}
class CategorySerializer:
category = serializers.CharField(source='get_category_display', required=False)
class Meta:
model = None
fields = {'category'}
class StateSerializer:
state = serializers.CharField(source='get_state_display', required=False)
class Meta:
model = None
fields = {'state'}
Is this possible or not?
You can do what you want with a factory function, although it's not completely trivial to get the internal variables (attributes) as you want them:
def factory(FIELDname):
class FIELDSerializer:
class Meta:
model = None
fields = {FIELDname}
settatr(FIELDSerializer, FIELDname, serializers.CharField(source=f'get_{FIELDname}_display', required=False))
return FIELDSerializer
CategorySerializer = factory('category')
StateSerializer = factory('state')
NameSerializer = factory('name')
The setattr allows us to set the name of attribute to the FIELDname string. (Thanks to #Code-Apprentice and #juanpa.arrivillaga for this idea.)
I don't know if there's any easy way to avoid the repetition of the field name and the desired class name when you call the factory without using something like exec (which is perfectly legal but usually leaves programmers with a bad taste in their mouths).

django rest framework - how to add a static field value for a serializer

I need to add a static field to my serializer. It should always return the same value, regardless of the passed object. Currently I implemented it like so:
class QuestionSerializer(serializers.ModelSerializer):
type = serializers.SerializerMethodField()
#staticmethod
def get_type(obj):
return 'question'
class Meta:
model = Question
fields = ('type',)
But is there a simpler way to do it, without the SerializerMethodField?
using a ReadOnlyField worked for me:
class QuestionSerializer(serializers.ModelSerializer):
type = serializers.ReadOnlyField(default='question')
class Meta:
model = Question
fields = ('type',)
https://www.django-rest-framework.org/api-guide/fields/#readonlyfield
The only alternative would be to override to_representation and add the value there:
def to_representation(self, obj):
data = super().to_representation(obj)
data['type'] = 'question'
return data
Not a much better option though.
You can use serializers.HiddenField
class QuestionSerializer(serializers.ModelSerializer):
type = serializers.HiddenField(default='question')
class Meta:
model = Question
fields = ('type',)
http://www.django-rest-framework.org/api-guide/fields/#hiddenfield
If you don't care about adding a line to your model, it would be easier to add static field to your model.
class Question(models.Model):
type = 'question'
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = ('type',)

django rest framework abstract class serializer

I have some models like these:
class TypeBase(models.Model):
name = models.CharField(max_length=20)
class Meta:
abstract=True
class PersonType(TypeBase):
pass
class CompanyType(TypeBase):
pass
Having this, I want to create just one serializer that holds all these field types (serialization, deserialization, update and save).
To be more specific, I want only one serializer (TypeBaseSerializer) that print the Dropdown on the UI, serialize the json response, deserialize it on post and save it for all my based types.
Something like this:
class TypeBaseSerializer(serializers.Serializer):
class Meta:
model = TypeBase
fields = ('id', 'name')
Is it possible?
I think the following approach is more cleaner. You can set "abstract" field to true for the base serializer and add your common logic for all child serializers.
class TypeBaseSerializer(serializers.ModelSerializer):
class Meta:
model = TypeBase
fields = ('id', 'name')
abstract = True
def func(...):
# ... some logic
And then create child serializers and use them for data manipulation.
class PersonTypeSerializer(TypeBaseSerializer):
class Meta:
model = PersonType
fields = ('id', 'name')
class CompanyTypeSerializer(TypeBaseSerializer):
class Meta:
model = CompanyType
fields = ('id', 'name')
Now you can use the both of these serializers normally for every model.
But if you really want to have one serializers for both the models, then create a container model and a serializer for him too. That is much cleaner :)
You can't use a ModelSerializer with an abstract base model.
From restframework.serializers:
if model_meta.is_abstract_model(self.Meta.model):
raise ValueError(
'Cannot use ModelSerializer with Abstract Models.'
)
I wrote a serializer_factory function for a similar problem:
from collections import OrderedDict
from restframework.serializers import ModelSerializer
def serializer_factory(mdl, fields=None, **kwargss):
""" Generalized serializer factory to increase DRYness of code.
:param mdl: The model class that should be instanciated
:param fields: the fields that should be exclusively present on the serializer
:param kwargss: optional additional field specifications
:return: An awesome serializer
"""
def _get_declared_fields(attrs):
fields = [(field_name, attrs.pop(field_name))
for field_name, obj in list(attrs.items())
if isinstance(obj, Field)]
fields.sort(key=lambda x: x[1]._creation_counter)
return OrderedDict(fields)
# Create an object that will look like a base serializer
class Base(object):
pass
Base._declared_fields = _get_declared_fields(kwargss)
class MySerializer(Base, ModelSerializer):
class Meta:
model = mdl
if fields:
setattr(Meta, "fields", fields)
return MySerializer
You can then use the factory to produce serializers as needed:
def typebase_serializer_factory(mdl):
myserializer = serializer_factory(
mdl,fields=["id","name"],
#owner=HiddenField(default=CurrentUserDefault()),#Optional additional configuration for subclasses
)
return myserializer
Now instanciate different subclass serializers:
persontypeserializer = typebase_serializer_factory(PersonType)
companytypeserializer = typebase_serializer_factory(CompanyType)
As already mentioned in Sebastian Wozny's answer, you can't use a ModelSerializer with an abstract base model.
Also, there is nothing such as an abstract Serializer, as some other answers have suggested. So setting abstract = True on the Meta class of a serializer will not work.
However you need not use use a ModelSerializer as your base/parent serializer. You can use a Serializer and then take advantage of Django's multiple inheritance. Here is how it works:
class TypeBaseSerializer(serializers.Serializer):
# Need to re-declare fields since this is not a ModelSerializer
name = serializers.CharField()
id = serializers.CharField()
class Meta:
fields = ['id', 'name']
def someFunction(self):
#... will be available on child classes ...
pass
class PersonTypeSerializer(TypeBaseSerializer, serializers.ModelSerializer):
class Meta:
model = PersonType
fields = TypeBaseSerializer.Meta.fields + ['another_field']
class CompanyTypeSerializer(TypeBaseSerializer, serializers.ModelSerializer):
class Meta:
model = CompanyType
fields = TypeBaseSerializer.Meta.fields + ['some_other_field']
So now since the fields name and id are declared on the parent class (TypeBaseSerializer), they will be available on PersonTypeSerializer and since this is a child class of ModelSerializer those fields will be populated from the model instance.
You can also use SerializerMethodField on the TypeBaseSerializer, even though it is not a ModelSerializer.
class TypeBaseSerializer(serializers.Serializer):
# you will have to re-declare fields here since this is not a ModelSerializer
name = serializers.CharField()
id = serializers.CharField()
other_field = serializers.SerializerMethodField()
class Meta:
fields = ['id', 'name', 'other_field']
def get_other_field(self, instance):
# will be available on child classes, which are children of ModelSerializers
return instance.other_field
Just iterating a bit over #adki's answer:
it is possible to skip model for TypeBaseSerializer;
derived serializers can refer to TypeBaseSerializer.Meta, so you would change them in a single place.
class TypeBaseSerializer(serializers.Serializer):
class Meta:
fields = ('id', 'name', 'created')
abstract = True
def func(...):
# ... some logic
class PersonTypeSerializer(TypeBaseSerializer):
class Meta:
model = PersonType
fields = TypeBaseSerializer.Meta.fields + ('age', 'date_of_birth')
class CompanyTypeSerializer(TypeBaseSerializer):
class Meta:
model = CompanyType
fields = TypeBaseSerializer.Meta.fields

Common variable in Django model's class Meta

I have a little problem with varaibles scope.
Code:
class Report(django_filters.FilterSet):
def __init__(self):
self.main_fields = []
class Meta:
model = ReportData
fields = self.main_fields
class OtherReport(ReportData):
fields_to_base_class = ['somefields']
class OtherReport2(ReportData):
fields_to_base_class = ['othersomefields']
How can I do this? Django doesn't see main_fields in Meta.

How can I subclass a modelform (extend with extra fields from the model)?

I have a model and a form:
class MyModel(models.Model):
field_foo = models.CharField(_("Foo"), max_length=50)
field_bar = models.IntegerField(_("Bar"))
class MyFormOne(forms.ModelForm):
class Meta:
model=MyModel
fields = ('field_foo', )
widgets = {'field_foo': forms.TextInput(attrs={'size': 10, 'maxlength': 50}),}
I would like to have another form MyFormTwo which would subclass that form by including also the field field_bar. My point is not to have to repeat the widget declaration for field_foo in the second form (DRY principle), and also not to have to repeat the list of fields from MyFormOne (in reality there are much more then one field then in the simple example above).
How should I define MyFormTwo?
You shouldn't have to explicitly declare the entire widget, just modify the attrs which are different.
Or if you have custom widgets in the real code, I would either
Create a custom model field class as well which uses that widget by default, if it's a general condition, so in the form class it just works "automagically".
If it's just form specific (not model specific), then for that case I'd just declare the form field explicitly on Form class, not in Meta and then inheritance applies in a straightforward way.
But with default widgets (with custom attrs) I'd try something like the following
class MyModel(models.Model):
field_foo = models.CharField(_("Foo"), max_length=50)
field_bar = models.IntegerField(_("Bar"))
class MyFormOne(forms.ModelForm):
class Meta:
model=MyModel
fields = ('field_foo', )
def __init__(*args, **kwargs):
super(MyFormOne, self).__init__(*args, **kwargs)
self.fields['field_foo'].widget.attrs['size'] = 10
# max_length should already be set automatically from model
# TextInput is also default widget for CharField
class MyFormTwo(MyFormOne):
class Meta:
model=MyModel
fields = MyFormOne.Meta.fields + ('field_foo2',)
def __init__(*args, **kwargs):
super(MyFormTwo, self).__init__(*args, **kwargs)
self.fields['field_foo2'].widget.attrs['size'] = 10
I'm not sure if this will work or not (it's completely untested with Django forms/models), but if it does let me know.
class MyModel(models.Model):
field_foo = models.CharField(_("Foo"), max_length=50)
field_bar = models.IntegerField(_("Bar"))
class MyFormOne(forms.ModelForm):
class Meta:
model=MyModel
fields = ('field_foo', )
widgets = {'field_foo': forms.TextInput(attrs={'size': 10, 'maxlength': 50}),}
class MyFormTwo(MyFormOne):
class Meta(MyFormOne.Meta):
fields = MyFormOne.Meta.fields + ('field_foo2',)
widgets = MyFormOne.Meta.widgets
# Add new fields as a dictionary
widgets.update({'field_foo2': forms.TextInput(attrs={'size': 10, 'maxlength': 50}),)
# Or add them one by one
widgets['field_foo3'] = forms.TextInput(attrs={'size': 10, 'maxlength': 50})
Again, I have no idea if that will help you. Please let me know what results you get.
This snippet is a bit old, but it explains the only way I've ever been able to get form multiple inheritance to work. It's pretty ugly.
http://djangosnippets.org/snippets/703/
There is also an open ticket regarding it: https://code.djangoproject.com/ticket/7018

Categories