I am trying to set a default value for attribute threshold in this code, the threshold should be the current level*50 and this is the model
class Level (models.Model):
name = models.CharField(max_length=20,null=True, blank=True)
description = models.CharField(max_length=20, null=True, blank=True)
number = models.IntegerField(null=True, blank=True)
threshold = models.IntegerField(null=True, blank=True,default=50*number,editable=False)
i get an error unsupported operand types for * : 'int' and 'IntegerField'
You best best is to do such calculation while saving the object. So override Model.save
or a better generic way would be to write a custom field and override pre_save
class DependentIntegerField(models.IntegerField):
def pre_save(self, model_instance, add):
if not add: # set the default only while adding model
return super(self, DependentIntegerField).pre_save(model_instance, add)
return model_instance.number*50
You can further enhance it and make DependentIntegerField generic so that you can pass callable to it and do any calculation, and you can do further enhancements like checking if user has set the value or not before using default value, and to make it more generic so that you can make any Field as dependent field by passing the field class to a factory function. e.g.
from django.db import models
class_map = {}
def depends_field_pre_save(self, model_instance, add):
"""
if default is not callable or it is not a model add, lets skip our hook
"""
if not add or not callable(self.default):
super(self.__class__, self).__init__(self,*args, **kwargs)
value = self.default(model_instance)
setattr(model_instance, self.attname, value)
return value
def FieldDepends(field_class):
"""
return a dervied class from field_class which supports dependent default
"""
if field_class in class_map:
# we already created this class so return that
return class_map[field_class]
new_class = type('Depends'+field_class.__name__, (field_class,), {'pre_save':depends_field_pre_save })
class_map[field_class] = new_class
return new_class
and use it like this
class DependentModel(models.Model):
def threshold_default(model_instance=None):
if model_instance is None:
return 10
return model_instance.number*10
number = models.IntegerField(null=True, blank=True, default=10)
threshold = FieldDepends(models.IntegerField)(null=True, blank=True, default=threshold_default,editable=False)
I have created a small django project djangodepends on bitbucket with test cases
You can override save method to calculation.
https://docs.djangoproject.com/en/dev/topics/db/models/#overriding-predefined-model-methods
class Level (models.Model):
name = models.CharField(max_length=20,null=True, blank=True)
description = models.CharField(max_length=20, null=True, blank=True)
number = models.IntegerField(null=True, blank=True)
threshold = models.IntegerField(null=True, blank=True ,editable=False)
def save(self, *args, **kwargs):
try:
self.threshold = self.number * 50
except TypeError:
pass
super(Level, self).save(*args, **kwargs) # Call the "real" save() method.
Related
I have a model that has a choice field with choices loaded in the runtime.
from some_utils import get_currency_options
class Product(models.Model):
name = models.CharField(max_length=20)
currency = models.CharField(max_length=3, null=True, blank=True, default="USD", choices=[])
def clean(self):
# getting the currency options at execution time, options may vary at different times
currency_code_options = get_currency_options()
if self.currency and self.currency not in currency_code_options:
raise ValidationError({"currency": f"Invalid currency code {self.fb_check_currency}."})
super(Product, self).clean()
Please ignore the bad design here, it was defined like this since we need to integrate with a legacy system.
In the Django admin, I have a form like this
from some_utils import get_currency_options
class ProductAdminForm(ModelForm):
currency_choices = get_currency_options()
#staticmethod
def _values_list_to_options(values_list):
return [(val, val) for val in values_list]
def __init__(self, *args, **kwargs):
super(ProductAdminForm, self).__init__(*args, **kwargs)
self.fields["currency"] = ChoiceField(choices=self._values_list_to_options(self.currency_choices))
class ProductAdmin(admin.ModelAdmin):
form = ProductAdminForm
Now the problem is, when I go to Django admin and want to update the currency option, it fails to save with an error saying currency is not a valid option. I understand this is due to the choice list being empty, I tried to override the clean and clean_all method but it didn't work.
Which method does the admin update operation trigger? Is there a way I can use the get_currency_options method to load currency options to the validator so if my selection matches one of the value, it passes the validator?
I have the same error. I your case, you need overview the clean_field method in your model class. For example:
from some_utils import get_currency_options
class Product(models.Model):
name = models.CharField(max_length=20)
currency = models.CharField(max_length=3, null=True, blank=True, default="USD", choices=[])
def clean_fields(self, exclude=None):
exclude = ['currency']
super().clean_fiedls(exclude=exclude)
def clean(self):
self.validate_currency()
super().clean()
def validate_currency(self):
# getting the currency options at execution time, options may vary at different times
currency_code_options = get_currency_options()
if self.currency and self.currency not in currency_code_options:
raise ValidationError({"currency": f"Invalid currency code {self.fb_check_currency}."})
I have a model built like this
class ApiPartner(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=30, verbose_name=_('Name'))
api_key = models.CharField(max_length=50, verbose_name=_('API key'), null=True)
secret_key = models.CharField(max_length=50, verbose_name=_('Secret key'), null=True)
client_key = models.CharField(max_length=50, verbose_name=_('Client key'), null=True)
endpoint = models.CharField(max_length=50, verbose_name=_('Endpoint'), null=True)
logo = models.ImageField(upload_to='logos/', null=True)
evey API partner has its own method to retrieve data, for example
def get_youtube_posts(endpoint,api_key):
results=list(requests.get(endpoint+'?key='+api_key).json())
return results[0:50]
def get_instagram_posts(endpoint,api_key,secret_key):
return requests.get(endpoint+'?key='+api_key+'&secret='+secret_key)
the question is: how do i assign the 'get_posts' function to the model so i can call a generic ApiPartner.get_posts() and it will retrieve the posts using the given function?
I'm thinking about like a models.FunctionField but i know that doesn't exist.
I think this is more a logical problem than a technical one but i can't find a way. Thank you
Maybe I'm understanding the question wrong; but you can just assign it as a property on the model class:
class MyModel(models.Model):
fields...
#property
def youtube_posts(self):
results=list(requests.get(self.endpoint+'?key='+self.api_key).json())
return results[0:50]
#property
def instagram_posts(self):
return requests.get(self.endpoint+'?key='+self.api_key+'&secret='+self.secret_key)
Then you can call it with the instance of your model.
mymodel = MyModel.objects.all().first()
youtube_posts = mymodel.youtube_posts
# or
instagram_posts = mymodel.instagram_posts
But this will only return one or the other since your models are based on one specific endpoint.
To create a more generic method on the model, use the above methods, plus this:
#property
def platform_posts(self)
if "instagram" in self.endpoint:
return self.instagram_posts
elif "youtube" in self.endpoint:
return self.youtube_posts
... You get the gist.
you need to get the filter using the get_related_filter class method
views
modelPath = 'Money.models'
app_model = importlib.import_module(modelPath)
cls = getattr(app_model, 'Money')
related_result = cls().get_related_filter(search_query='search_query')
models.py
class Money(models.Model):
money = models.DecimalField(max_digits=19, blank=True, default=0, decimal_places=2)
def get_related_filter(self, **kwargs):
results = super(Money, self).objects.filter(Q(money__icontains=kwargs['search_query']))
return results
def __str__(self):
return self.money
why gives 'super' object has no attribute 'objects' Python Django, and does not return filter
It makes no sense to work with super(Money, self) for two reasons:
this proxy object will resolve to Model, but Model nor it parents have an objects attribute; and
even if that was the case, you can only access .objects on a model class, not the instance.
You thus can filter with:
class Money(models.Model):
money = models.DecimalField(max_digits=19, blank=True, default=0, decimal_places=2)
def get_related_filter(self, search_query, **kwargs):
return Money.objects.filter(money__icontains=search_query)
def __str__(self):
return str(self.money)
The __str__ is also supposed to return a string, not a decimal, so you should return str(self.money), not self.money.
Problem :
So, I have been trying to make an object of the model Trade with an initial value to the identifier from my custom function gen_rand_string().
But the problem is when, I am initialising it.
If I remove the initializer and set the UUIDField to NULL, it works out to be fine.
This is the error, I am getting :
TypeError at /admin/account/trade/add/
int() argument must be a string or a number, not 'Trade'
My Trade class :
class Trade(models.Model):
NEW_TRADE = 'N'
CANCELLED_TRADE = 'C'
PENDING_TRADE = 'P'
STATUS_OF_TRADE = (
(NEW_TRADE, "New"),
(CANCELLED_TRADE, "Cancelled"),
(PENDING_TRADE, "Pending")
)
TYPE_BUY = 'B'
TYPE_SELL = 'S'
TYPE_OF_TRADE = (
(TYPE_BUY, "Buy"),
(TYPE_SELL, "Sell")
)
user = models.OneToOneField('UserProfile', related_name='trades')
identifier = models.UUIDField(null=False, editable=False)
created_at = models.DateTimeField(auto_now_add=True, editable=False)
finished_at = models.DateTimeField(auto_now_add=True)
amount = models.DecimalField(max_digits=19, decimal_places=10, null=False)
type = models.CharField(max_length=2, choices=TYPE_OF_TRADE, null=False)
status = models.CharField(max_length=2, choices=STATUS_OF_TRADE, default=PENDING_TRADE, null=False)
def __init__(self, *args, **kwargs):
self.identifier = gen_rand_string()
super(Trade, self).__init__(self, *args, **kwargs)
class Meta:
ordering = ('created_at',)
def __unicode__(self):
return self.identifier
def __str__(self):
return self.identifier
My gen_rand_string() :
def gen_rand_string(purpose=''):
if purpose:
return purpose + '_' + get_random_string(length=64 - len(purpose))
else:
return get_random_string(length=64)
Suggestions :
I am making a random string for each trade in not a better way, would someone suggest somewhat better option, or something they would have it in their place.
I am using python 2.7 which is the reason of my initialisation of the object in a different way
Thanks.
You are discouraged from overriding __init__ for Django models. You use your gen_rand_string as the field's default:
identifier = models.UUIDField(null=False, editable=False, default=gen_rand_string)
However you probably don't need to define your own gen_rand_string method - just use uuid.uuid4, as in docs for UUIDField.
identifier = models.UUIDField(null=False, editable=False, default=uuid.uuid4)
problem is with your init function syntax.
def __init__(self, *args, **kwargs):
super(Trade, self).__init__(self, *args, **kwargs)
self.identifier = gen_rand_string()
I'm playing around in Django, and wondering if there is a way to loop through instances of two different models I have created?
/ models.py:
class Tran(models.Model):
name = models.CharField(max_length=300)
description = models.CharField(max_length=2000)
type = models.ForeignKey(TransactionType)
def __str__(self):
return self.name
class DocLink(models.Model):
trann = models.ForeignKey(Transaction)
t_link = models.CharField(max_length=2000)
t_display = models.CharField(max_length=1000)
p_display = models.CharField(max_length=300)
p_link = models.CharField(max_length=2000)
def __str__(self):
return self.link
What I want to do:
Look through each of the Tran instances and create a default value for the links/displays in the DocLink table instead of doing it manually.
Is there anyway I can be pointed in the right direction?
If you want to set links/displays default value in DocLink instance based on trann field you can override model's save method.
For example following code shows how to set t_link if it doesn't have a value:
class DocLink(models.Model):
trann = models.ForeignKey(Transaction)
t_link = models.CharField(max_length=2000)
t_display = models.CharField(max_length=1000)
p_display = models.CharField(max_length=300)
p_link = models.CharField(max_length=2000)
def __str__(self):
return self.link
def save(self, *args, **kwargs):
if not self.t_link:
pass # TODO set self.t_link based on trann
super(DocLink, self).save(*args, **kwargs)
Also you can change model's trann field to:
trann = models.ForeignKey(Transaction, related_name="doclinks")
And then access to all DocLinks of a Tran with:
# t is an instance of Tran class
t.doclinks.all()
So you can loop through this list and do what you want.