How to create property methods in django models dynamically? - python

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

Related

How to add a custom method to a model field in Django?

I have two models that will use the same CardNumberField() to store credit card numbers. How can I add a custom method to the field to mask the card numbers?
I have created the CardNumberField() which inherits from models.Charfield:
# CARD NUMBER FIELD
class CardNumberField(models.CharField):
description = _('card number')
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 19
super().__init__(*args, **kwargs)
The CardNumberField() is then imported and used in my customers/models.py:
# CARD MODEL
class Card(models.Model):
number = CardNumberField()
...
def __str__(self):
return 'Card [{number}]'.format(number=self.number)
...and in my transactions/models.py:
# TRANSACTION MODEL
class Transaction(models.Model):
card_number = CardNumberField()
...
def __str__(self):
return 'Transaction ...'
So, how can I add the following method to my CardNumberField() to be used by both of my models?
def masked_number(self):
# display masked card number
number = self.number
return number[-4:].rjust(len(number), '#')
Also, how will I grab this field method in a DRF serializer class?
You can override the contribute_to_class method to not only contribute the field, but also include an extra method:
from functools import partialmethod
def _mask_number(self, field):
number = getattr(self, field.attname)
return number[-4:].rjust(len(number), '#')
# CARD NUMBER FIELD
class CardNumberField(models.CharField):
description = _('card number')
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 19
super().__init__(*args, **kwargs)
def contribute_to_class(self, cls, name, **kwargs):
super().contribute_to_class(cls, name, **kwargs)
setattr(
cls, f'masked_{self.name}',
partialmethod(_mask_number, field=self)
)
If you add a field foo to a model class, it will automatically add a masked_foo method to that class. This thus also means that if you have two or more CardNumberFields, it will add two or more masked_foo methods.
Use an abstract model instead:
class ModelWithCardNumber(models.Model):
card_number = models.CharField(max_length=19)
#property
def masked_number(self):
return self.card_number[-4:].rjust(len(number), '#')
class Meta:
abstract = True
class Card(ModelWithCardNumber):
def __str__(self):
return 'Card [{number}]'.format(number=self.number)
class Transaction(ModelWithCardNumber):
def __str__(self):
return 'Transaction ...'
Now in your serializer you can access Card.masked_number and Transaction.masked_number.

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 inheritance from models.CharField with choices gives error

I have two classes which are used in application logic. One is called Direction and the other is called Compass. Direction is a member of Compass. What I am trying to implement is a modelField that wraps the Direction class and that I can use as a member in the Compass model. The DirectionField class inherits from models.CharField and sets choices from the parent class.
I think that this is a nice design because I can use the DirectionField in many other classes and it's easy to maintain. However, I get an error when I save the Compass model in the Admin page in Django. The error is
"Value is not a valid choice."
I use Python 2.7 and Django 1.4.
Could someone please review this issue and suggest what the problem is and how I could resolve it.
Here is the source:
class Direction():
choices = (('N','North'),
('S','South'),
('E','East'),
('W','West'),)
def __init__(self, value=None):
self.value = value
class DirectionField(models.CharField):
def __init__(self, *args, **kwargs):
super(DirectionField, self).__init__(choices=Direction.choices,
*args, **kwargs)
__metaclass__ = models.SubfieldBase
def to_python(self, value):
if isinstance(value, Direction) or value is None:
return value
return Direction(value)
def get_prep_value(self, value):
return value.value
class Compass(models.Model):
name = models.CharField(max_length=20)
direction = modelFields.DirectionField(max_length=10)
class Meta:
db_table = 'campass'
def __unicode__(self):
return "%s/%s" % (self.name, self.direction)
class CompassForm(forms.ModelForm):
class Meta:
model = Compass
def clean(self):
return self.cleaned_data
Error in the Admin page (or form view) that I get when I save Compass:
Value <src.bo.tgEnum.Direction instance at 0x03E97E18> is not a valid choice.
To pass field validation you need to add this functions to Direction class:
def __eq__(self, value):
return self.value == value
def __len__(self):
return len(self.value)
Because it compares value with choices keys and value has Dictionary type, key is string.

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() #

Dynamically add base class?

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__)

Categories

Resources