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

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),
}

Related

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.

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

Django Form field initial value when updating an instance

I have a custom Django ModelForm that I use to update a model instance.
This is the example model:
class MyModel(models.Model):
number = models.CharField(_("Number"), max_length=30, unique=True)
sent_date = models.DateField(_('Sent date'), null=True, blank=True)
When creating an instance I will pass only the number field, that is why I don't want the sent_date to be required.
Then I have a view that updates the sent_date field, using this custom form:
# Generic form updater
class MyModelUpdateForm(forms.ModelForm):
class Meta:
model = MyModel
fields = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make fields mandatory
if hasattr(self, 'required_fields'):
for field_name in self.required_fields:
self.fields[field_name].required = True
# Set initial values
if hasattr(self, 'initial_values'):
for field_name, value in self.initial_values.items():
self.initial[field_name] = value
class SentForm(MyModelUpdateForm):
required_fields = ['sent_date']
initial_values = {'sent_date': datetime.date.today()}
class Meta(MyModelUpdateForm.Meta):
fields = ['sent_date']
field_classes = {'sent_date': MyCustomDateField}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
MyModelUpdateForm is a generic ancestor for concrete forms like SentForm.
In my view whenever there is a GET I manually instantiate the form with:
my_form = SentForm({instance: my_model_instance})
So in this case I would expect the sent_date field to have an initial value set to today's date even tough the real model instance field is None.
If I inspect my_form object it does indeed have these attributes:
initial: {'sent_date': datetime.date(2018, 3, 1)}
instance: my_model_instance
fields: {'sent_date':
...: ...,
'initial': None # Why this is None?
...: ...
}
So apparently it should work but it doesn't: the field is always empty.
So I suspect that the value is coming from my_model_instance.sent_date that is in fact None.
The initial['sent_date'] = datetime.date(2018, 3, 1) is correct.
On the other side fields['sent_date']['initial'] = None it's not.
How can I always show the initial value when my_model_instance.sent_date is None?
Apparently I've solved with:
class MyModelUpdateForm(forms.ModelForm):
class Meta:
model = MyModel
fields = []
def __init__(self, *args, **kwargs):
initial = kwargs.get('initial', {})
if hasattr(self, 'initial_values') and not kwargs.get('data'):
for field_name, value in self.initial_values.items():
if not getattr(kwargs.get('instance', None), field_name, None):
initial[field_name] = value
kwargs.update({'initial': initial})
super().__init__(*args, **kwargs)
# Make fields mandatory
if hasattr(self, 'required_fields'):
for field_name in self.required_fields:
self.fields[field_name].required = True
Even tough it works I wouldn't mind a less hackish solution if anyone has any :)
I have this case in many places in my app without having any problem. However, I use a different way to set up initial value of some fields of an existing instance. Instead of:
self.initial[field_name] = value
I write, after having called super():
self.fields[field_name].initial = value
Can you try and tell the result ?

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.

approaching django model fields through a field name mapping

Django newbie here,
I have several types of models, in each of them the fields have different names (e.g. first_name, forename, prenom) and I want each of the models to contain a mapping so that I can easily approach each of the fields using one conventional name (e.g. first_name for all of the field names). what's a good way of doing this?
I think the best way would be to use conventional names in your models and provide only one obvious way to access it. If you don't wan't to change the database columns too, you can use the db_column option. Example:
class Person(models.Model):
first_name = models.CharField(max_length=255, db_column='prenom')
class Customer(models.Model):
first_name = models.CharField(max_length=255, db_column='forename')
class Worker(models.Model):
first_name = models.CharField(max_length=255) # column is also called "first_name"
If you need to provide different ways to access the members (which I would try to avoid!) you can still add properties to each model.
You could define a #property on each of your models, like this:
class Personage(models.Model):
prenom = models.CharField(max_length=255)
#property
def first_name(self):
return self.prenom
then you can just reference your new property like this:
personage = Personage.objects.all()[0]
personage.first_name
You could make your models all inherit a new model that has a property function defined to get/set the right variable.
class BaseNameClass(models.Model)
def getfname(self):
if hasattr(self, 'first_name'): return self.first_name
if hasattr(self, 'prenom'): return self.prenom
if hasattr(self, 'forename'): return self.forename
def setfname(self, x):
if hasattr(self, 'first_name'): self.first_name = x
if hasattr(self, 'prenom'): self.prenom = x
if hasattr(self, 'forename'): self.forename = x
firstname = property(getfname, setfname)
And then change your models to all inherit from that. It will be slightly slower but we're talking nano and milliseconds.
If you had an descendant object called person, you'd access the name simply by:
print person.firstname
person.firstname = "oli"
print person.firstname

Categories

Resources