Django inheritance from models.CharField with choices gives error - python

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.

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

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 can I make a field case insensitive and unique only?

I'm trying to add object to my models that are unique and case insensitive so if I add 'car' first and then I try to add 'cAr' it should return an error. I don't know how to that, though.
How can I make this happen?
This is the model:
class Food(models.Model):
name = models.CharField(max_length=100, unique=True)
def __str__(self):
return self.name
This is the serializer:
class FoodSerializer(serializers.ModelSerializer):
class Meta:
model = Food
fields = '__all__'
You can Make your custom LowerCharField and Use it in your Models Just import this class
and car and Car can be checked for uniqueness.
Here is my solution.
class LowerCharField(with_metaclass(models.SubfieldBase, models.CharField)):
def __init__(self, *args, **kwargs):
self.is_lowercase = kwargs.pop('lowercase', False)
super(LowerCharField, self).__init__(*args, **kwargs)
def get_prep_value(self, value):
value = super(LowerCharField, self).get_prep_value(value)
if self.is_lowercase:
return value.lower()
return value
And You can do this way
class Food(models.Model):
name =LowerCharField(max_length=128, lowercase=True, null=False, unique=True)
def __str__(self):
return self.name
If you want further customization like you may take any input from user i.e car or CAR at the end you may convert to lower and check for uniqueness.

How to call the property method in a dynamic Django model?

I have a problem with calling a property() inside a dynamic model in Django. I need an additional field, which gives me a log-transformed value of an existing field of a database-table. For further processing the field must be accessible through the model structure. The name of the database-table will be defined during runtime, so that I need a dynamic model.
This is my model definition:
def create_model(self, name, libs=None, app_label='gotool', module='', options=None, admin_opts=None):
fields = {
'database_id': models.CharField(max_length=255, primary_key=True),
'description': models.TextField(blank=True),
'l1_normalized': models.FloatField(null=True, blank=True),
'l2_normalized': models.FloatField(null=True, blank=True),
'pvalue': models.FloatField(null=True, blank=True),
'log2fc': models.FloatField(null=True, blank=True),
'goterm': models.TextField(db_column='GOTerm', blank=True),
'_get_l1_logcount': lambda self: numpy.log10(self.l1_normalized),
# here is my problem:
'l1_s_logcount': property(_get_l1_logcount), # I don't know how to call the property-method inside the dynamic model definition
}
class Meta:
pass
if app_label:
setattr(Meta, 'app_label', app_label)
if options is not None:
for key, value in options.iteritems():
setattr(Meta, key, value)
attrs = {'__module__': module, 'Meta': Meta}
if fields:
attrs.update(fields)
model = type(name, (models.Model,), attrs)
if admin_opts is not None:
class Admin(admin.ModelAdmin):
pass
for key, value in admin_opts:
setattr(Admin, key, value)
admin.site.register(model, Admin)
return model
Many thanks for your help!
Think about a way in which you could implement it in a class:
class X(object):
def get_y(self):
return self.y
If you were wanting to create a property y without actually using the function get_y as it stands at that point, how might you do it?
class X(object):
def get_y(self):
return self.y
#property
def y(self):
return self.get_y()
This way it is evaluated at runtime.
'l1_s_logcount': property(lambda self: self._get_l1_logcount()),
Or, cutting out the intermediate step,
'l1_s_logcount': property(lambda self: numpy.log10(self.l1_normalized)),
Also you could take out the function and use it directly
get_l1_logcount = lambda self: numpy.log10(self.l1_normalized)
fields = {
...,
'_get_l1_logcount': get_l1_logcount,
'l1_s_logcount': property(get_l1_logcount),
}

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