Dynamically add base class? - python

Let's say I have a base class defined as follows:
class Form(object):
class Meta:
model = None
method = 'POST'
Now a developer comes a long and defines his subclass like:
class SubForm(Form):
class Meta:
model = 'User'
Now suddenly the method attribute is lost. How can I "get it back" without forcing the user to inherit his meta class from mine? Can I dynamically add a base class to Form.Meta in the initializer, or in a metaclass's __new__ func?

As long as they won't override your __init__, or it will be called (ie by super), you can monkey-patch the Meta inner class:
class Form(object):
class Meta:
model = None
method = "POST"
def __init__(self, *args, **kwargs):
if self.__class__ != Form:
self.Meta.__bases__ += (Form.Meta,)
# other __init__ code here.
class SubForm(Form):
class Meta:
model = 'User'

Do you really need Meta to be defined that way? If you only need to access it as form.Meta.method, why wouldn't you just use a dotdict?
class dotdict(dict):
def __getattr__(self, attr):
return self.get(attr, None)
__setattr__= dict.__setitem__
__delattr__= dict.__delitem__
Then you can do this:
class Form(object):
def __init__(self):
self.Meta = dotdict()
self.Meta.model = None
self.Meta.method = 'POST'
class SubForm(Form):
def __init__(self):
Form.__init__(self)
self.Meta.model = 'User'

Maybe you could use a metaclass like this:
class _Meta:
model = None
method = "Post"
class MetaForm(type):
def __init__(cls, name, bases, dct):
super(MetaForm, cls).__init__(name, bases, dct)
if hasattr(cls, 'Meta'):
meta = getattr(cls, 'Meta')
for k,v in _Meta.__dict__.items():
check = meta.__dict__.get(k)
if not check:
meta.__dict__[k] = v
else:
setattr(cls, "Meta", _Meta)
class Form(object):
__metaclass__ = MetaForm
class SubForm(Form):
class Meta:
model = 'User'
class Sub2Form(Form):
pass
sub_form = SubForm()
sub2_form = Sub2Form()
print sub_form.Meta.method # prints "Post"
print sub2_form.Meta.model # prints None
The code is really simple and maybe you need to suit it to your needs.

You can check for method attribute in the __init__ method of a parent object and update it if needed. Of course this will work only if the programmer you are protecting your code from will call it in his constructor.
class Form(object):
def __init__(self):
if not getattr(self.Meta,'method',False):
self.Meta.method='POST'
class Meta:
model = None
method = 'POST'
class SubForm(Form):
class Meta:
model = 'User'

Maybe I could omit the default Meta class inside Form and use a default dict instead?
meta_defaults = {'model':None, 'method':'POST'}
meta_vars = meta_defaults
meta_vars.update(Form.Meta.__dict__)

Related

How to create property methods in django models dynamically?

I am creating property methods for every model where the model attribute includes ImageField or FileField. So, I decided to make an abstract model where I check the fields in the model and if there are any ImageField and FileField in the model the property method creates it automatically by itself.
I usually add '_url' to the attribute when I create the method
Below is what I do usually
class MyModel(models.Model):
image = ImageField(...)
file = FileField(...)
...
#property
def image_url(self):
if self.image and hasattr(self.image, 'url'):
return self.image.url
#property
def file_url(self):
if self.file and hasattr(self.file, 'url'):
return self.file.url
...
Below what I did so far
class MyModel(models.Model):
...
def __new__(cls, value):
fields = self._meta.get_fields()
for field in fields:
if isinstance(field, ImageField) or isinstance(field, FileField):
???
Any suggestions?
Use mixins.
class ImageUrlMixin:
#property
def image_url(self):
if self.image and hasattr(self.image, "url"):
return self.image.url
class FileUrlMixin:
#property
def file_url(self):
if self.file and hasattr(self.file, "url"):
return self.file.url
class FileImageUrlMixin(FileUrlMixin, ImageUrlMixin):
pass
class OnlyHasFileFieldModel(FileUrlMixin, models.Model):
# ..model implementation
class OnlyHasImageFieldModel(ImageUrlMixin, models.Model):
# ..model implementation
class HasBothFileAndImageFieldModel(FileImageUrlMixin, models.Model):
# ..model implementation
Or if you want to support fields dynamically e.g. my_model.arbitrary_field_url:
class DynamicFieldUrlMixin:
def __getattr__(self, name):
if name.endswith("_url"):
field_name = "".join(name.split("_")[:-1])
field = getattr(self, field_name, None)
if hasattr(field, "url"):
return field.url
raise AttributeError

DRF SerializerMethodField how to pass parameters

Is there a way to pass paremeters to a Django Rest Framework's SerializerMethodField?
Assume I have the models:
class Owner(models.Model):
name = models.CharField(max_length=10)
class Item(models.Model):
name = models.CharField(max_length=10)
owner = models.ForeignKey('Owner', related_name='items')
itemType = models.CharField(max_length=5) # either "type1" or "type2"
What I need is to return an Owner JSON object with the fields: name, type1items, type2items.
My current solution is this:
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = models.Item
fields = ('name', 'itemType')
class OwnerSerializer(serializers.ModelSerializer):
type1items = serializers.SerializerMethodField(method_name='getType1Items')
type2items = serializers.SerializerMethodField(method_name='getType2Items')
class Meta:
model = models.Owner
fields = ('name', 'type1items', 'type2items')
def getType1Items(self, ownerObj):
queryset = models.Item.objects.filter(owner__id=ownerObj.id).filter(itemType="type1")
return ItemSerializer(queryset, many=True).data
def getType2Items(self, ownerObj):
queryset = models.Item.objects.filter(owner__id=ownerObj.id).filter(itemType="type2")
return ItemSerializer(queryset, many=True).data
This works. But it would be much cleaner if I could pass a parameter to the method instead of using two methods with almost the exact code. Ideally it would look like this:
...
class OwnerSerializer(serializers.ModelSerializer):
type1items = serializers.SerializerMethodField(method_name='getItems', "type1")
type2items = serializers.SerializerMethodField(method_name='getItems', "type2")
class Meta:
model = models.Owner
fields = ('name', 'type1items', 'type2items')
def getItems(self, ownerObj, itemType):
queryset = models.Item.objects.filter(owner__id=ownerObj.id).filter(itemType=itemType)
return ItemSerializer(queryset, many=True).data
In the docs SerializerMethodField accepts only one parameter which is method_name.
Is there any way to achieve this behaviour using SerializerMethodField? (The example code here is overly simplified so there might be mistakes.)
There is no way to do this with the base field.
You need to write a custom serializer field to support it. Here is an example one, which you'll probably want to modify depending on how you use it.
This version uses the kwargs from the field to pass as args to the function. I'd recommend doing this rather than using *args since you'll get more sensible errors, and flexibility in how you write your function/field definitions.
class MethodField(SerializerMethodField):
def __init__(self, method_name=None, **kwargs):
# use kwargs for our function instead, not the base class
super().__init__(method_name)
self.func_kwargs = kwargs
def to_representation(self, value):
method = getattr(self.parent, self.method_name)
return method(value, **self.func_kwargs)
Using the field in a serializer:
class Simple(Serializer):
field = MethodField("get_val", name="sam")
def get_val(self, obj, name=""):
return "my name is " + name
>>> print(Simple(instance=object()).data)
{'field': 'my name is sam'}
You could just refactor what you have:
class OwnerSerializer(serializers.ModelSerializer):
type1items = serializers.SerializerMethodField(method_name='getType1Items')
type2items = serializers.SerializerMethodField(method_name='getType2Items')
class Meta:
model = models.Owner
fields = ('name', 'type1items', 'type2items')
def getType1Items(self, ownerObj):
return getItems(ownerObj,"type1")
def getType2Items(self, ownerObj):
return getItems(ownerObj,"type2")
def getItems(self, ownerObj, itemType):
queryset = models.Item.objects.filter(owner__id=ownerObj.id).filter(itemType=itemType)
return ItemSerializer(queryset, many=True).data

How to use DRF Custom serializer field with DB model

I'm using relation database which is having Binary Field, So how can I use DRF serializer to save the field value
I have referred the documentation https://www.django-rest-framework.org/api-guide/fields/#custom-fields
and understood some of the part and created below, but I'm not sure how to use it in serializer
Model
class MyData(models.Model):
data = models.BinaryField()
Custom Field
class BinaryField(serializers.Field):
def to_representation(self, value):
return value.decode('utf-8')
def to_internal_value(self, value):
return value.encode('utf-8')
But how should I use this in my below serializer
class BlobDataSerializer (serializers.ModelSerializer):
class Meta:
model = MyData
fields = ('id', 'data')
So basically I'm trying to store incoming data in binary field. Thanks in advance
Like this:
class BlobDataSerializer (serializers.ModelSerializer):
class Meta:
model = MyData
fields = ('id', 'data')
data = BinaryField()
For a more reusable solution, you could also subclass ModelSerializer and customize the serializer_field_mapping.
See https://www.django-rest-framework.org/api-guide/serializers/#customizing-field-mappings
What about using Serializer Method Field
class BlobDataSerializer(serializers.ModelSerializer):
data = serializers.SerializerMethodField()
def get_data(self, obj):
return obj.decode('utf-8')
class Meta:
model = MyData
fields = ('id', 'data')
Explicitly per field
In the serializer, specify the field type for each custom field.
class BlobDataSerializer (serializers.ModelSerializer):
class Meta:
model = MyData
fields = ('id', 'data')
data = BinaryField()
Implicitly for all fields
For a more reusable solution, you could also subclass ModelSerializer and customize the serializer_field_mapping.
Because we need to override a class variable (not an instance variable), it's not as straightforward as the above solution and it's also kind of "magical".
class BlobDataSerializerMetaClass(type(serializers.ModelSerializer)):
def __new__(cls, clsname, bases, attrs):
# Call the __new__ method from the ModelSerializer metaclass
super_new = super().__new__(cls, clsname, bases, attrs)
# Modify class variable "serializer_field_mapping"
# serializer_field_mapping: model field -> serializer field
super_new.serializer_field_mapping[models.BinaryField] = BinaryField
return super_new
# Set the above metaclass as the serializer metaclass
class BlobDataSerializer (serializers.ModelSerializer, metaclass=BlobDataSerializerMetaClass):
class Meta:
model = MyData
fields = ('id', 'data')
See https://www.django-rest-framework.org/api-guide/serializers/#customizing-field-mappings

Django Meta Class Access Outer Attribute

Folks,
I need to implement a form that may vary a little depending on a variable. My class that subclasses ModelForms looks like this
class ConstantVwModelForm(forms.ModelForm):
#couple attributes
def __init__(self, hasData, *args, **kwargs):
class Meta:
fields = ('xx', 'yy' ..)
I am looking for the very best way to access the variable hasData from the inner class Meta, it would be like
class ConstantVwModelForm(forms.ModelForm):
#couple attributes
def __init__(self, hasData, *args, **kwargs):
class Meta:
if hasData:
fields = ('xx', 'yy', ..)
else:
fields = ('hh', ..)
Any help is highly appreciated
You shouldn't do that and there's no way to achieve this. You can delete field on the fly in your __init__ function explicitly:
class ConstantVwModelForm(forms.ModelForm):
#couple attributes
def __init__(self, hasData, *args, **kwargs):
if hasData:
del self.fields['hh']
else:
del self.fields['xx']
del self.fields['yy']
class Meta:
model = ConstantVwModel

Python - make class decorator work on derived classes

In the app we're developing using Django, in some cases we need to automatically assign permissions to users for some models, that has owners (there is no rule for field's name, it can be "user", "owner", "coach" etc., also there can by more than one field.) My solution is to create a decorator containing those fields names, that will be put before model definition, like this (not using django-specific code in samples):
#auto_assign_perms('owner', 'user')
class Test(Base):
pass
Let's assume that Base is an abstract class deriving after Django's Model class, where I add functionality to assign permissions after object is saved. For now I only print a list of users assigned to the class. Below you can find code for the decorator and Base class:
class auto_assign_perms(object):
def __init__(self, *users):
self.users = users
def __call__(self, cls):
cls.owners.update(self.users)
return cls
class Base(object):
owners = set()
def save(self, *args, **kwargs):
for owner in self.owners:
print owner,
print
And my models could look like this:
#auto_assign_perms('owner', 'user')
class Test(Base):
pass
#auto_assign_perms('coach')
class Test2(Base):
pass
The problem is that both child classes contains all three fields ('owner', 'user', 'coach'), altough print self.__class__.__name__ in Base.save() method properly shows "Test" or "Test2". I tried to add classmethod get_owners() in Base class and then iterating over its results, but it doesn't helps.
How can I solve this? Maybe I should use metaclasses (I don't get them yet)? Thanks in advance.
You need to set the list of owners, not update:
class auto_assign_perms(object):
def __init__(self, *users):
self.users = users
def __call__(self, cls):
cls.owners = set(self.users) # <- here
return cls
#some tests
#auto_assign_perms('owner', 'user')
class Test(Base):
pass
#auto_assign_perms('coach')
class Test2(Base):
pass
t = Test()
t.save()
t = Test2()
t.save()
>>>
owner user
coach
You are using owners as a class variable of Base so whenever you change owners the change will be seen in all the derived classes.
To fix that you should define the owners variable as class variable of the derived classes:
class Base(object):
def save(self, *args, **kwargs):
for owner in self.owners:
print owner,
print
#auto_assign_perms('owner', 'user')
class Test(Base):
owners = set()
#auto_assign_perms('coach')
class Test2(Base):
owners = set()
Call me paranoia but i find this solution more elegant and that because i don't think you need owners to be a class variable at all:
def auto_assign_perms(*users):
def class_wrapper(cls):
class ClassWrapper(cls):
def __init__(self, owners=users):
super(cls, self).__init__(owners=owners)
ClassWrapper.__name__ = cls.__name__
ClassWrapper.__module__ = cls.__module__
return ClassWrapper
return class_wrapper
class Base(object):
def __init__(self, owners=None):
if owners is None:
owners = set()
self.owners = owners
def save(self, *args, **kwargs):
for owner in self.owners:
print owner,
print
#auto_assign_perms('owner', 'user')
class Test1(Base):
pass
#auto_assign_perms('coach')
class Test2(Base):
pass
class Test3(Base):
pass
t = Test1(); t.save() # owner user
t = Test2(); t.save() # coach
t = Test3(); t.save() #

Categories

Resources