Django: How to Set Default Field Value By Model Method - python

I have a simple Model. I want to calculate a default value for one of its fields (let's call it score) based on some of the fields of the same model:
My ideal way to do it is as so:
class ModelA(models.model):
field_1 = models.IntegerField()
field_1 = models.IntegerField()
score = models.IntegerField(default=self.calculate_default())
def calculate_default(self):
default = (self.field_1 + self.field_2) / 2
return default
Problem: the calculate_default method does not have access to self.
One solution I tried was overriding the save() method. The problem is that it prevents the user to further change the score field.
Another method I searched was to override the inti of the class. But according to django it might have consequences if not implemented correctly.
What is the cleanest way to achieve this? How can I set the default of a field to be calculated from other fields of the same model such that it could be later changed by the user?
I have a hunch that classmethod is the way to go but I haven't been able to pull it off yet.

the calculate_default method does not have access to self. One solution I tried was overriding the save() method. The problem is that it prevents the user to further change the score field.
Actually its the right approach. But you can put a check in save() method if the value of score is empty or not:
def save(self, *args, **kwargs):
if self.score == None:
self.score = self.calculate_default()
super().save(*args, **kwargs)
FYI, in classmethod, you can't access self or the ModelA object. If the class method you intend to use is something not dependent on the current instance of ModelA's fields or totally independent value(for example: current date datetime.now), then you can use that.

Related

Django Custom Manager to dynamically filter archived objects

Suppose I have a model:
class Car(models.Model):
name = models.CharField(max_length=50)
is_active = models.BooleanField(default=True)
When I query on Car, I always want to return the objects satisfying, is_active=True.
For this, searching on StackOverFlow, I get that my best bet is to use ModelManager, like this:
class CarManager(models.ModelManager):
def get_queryset(self):
return super().get_queryset().filter(is_active=True)
And, use this Manager in my model.
class Car(models.Model):
name = models.CharField(max_length=50)
is_active = models.BooleanField(default=True)
objects = CarManager()
Using this solution always returns active Car queryset.
But, sometimes I want to return inactive Car queryset as well, and I don't want to write another ModelManager.
To elaborate,
When I run,
Car.objects.all()
or,
Car.objects.filter(name__contains='Car')
or,
Car.objects.filter(is_active=True)
I only want active Car queryset.
When I run,
Car.objects.filter(is_active=False)
I want to have inactive Car queryset.
And, I want to achieve this using single ModelManager and default methods (get, filter, all, etc). Why I want this is because, it has been used in many places already.
So, is there any way to achieve this? Any suggestions or insights are heartily welcome.
Thanks for your help in advance.
So, after a long research and going through documentations and Django source code, I have come up with this for filter() method:
class CarManager(models.ModelManager):
def filter(self, *args, **kwargs):
if kwargs.get('is_active') == False:
return super().get_queryset().filter(*args, **kwargs)
return self.get_queryset().filter(*args, **kwargs)
def get_queryset(self):
return super().get_queryset().filter(is_active=True)
Here, I have overriden filter() method, so that:
If is_active=False is passed, then the parent (default) get_queryset() followed by filter() is called.
If is_active is not passed or is_active=True is passed, then the overridden get_queryset() method is called (which returns the active Car queryset) followed by the filter() method.
If there are any other solutions or better practices, please do mention them. Thanks.
I am afraid is not possible since you are overwriting the manager's base queryset. What you could do instead of creating another manager is to implement an extra method that returns a queryset with only inactive cars, something like:
class CarManager(models.ModelManager):
def get_queryset(self):
return super().get_queryset().filter(is_active=True)
def get_inactive_cars(self):
return super().get_queryset().filter(is_active=False)
And then replace the querysets where you retrieve the inactive ones:
Car.objects.get_inactive_cars()

Django sync one-to-one models

I simplify my code structure, which contains two models:
# created by third part app, not Django one
# but we share same DB, so i have access to this one
class A(models.Model):
title = models.TextField()
# other fields ...
class Meta:
manage = False
class B(models.Model):
model_a = models.OneToOneField(A, related_name='+')
# other fields, to extend model A functionality
Is this a good way to extend third part app model A with my additional fields and methods? Now i have problem to sync this models true one-to-one field. Since I don't have access to trigger model A creation.
In ideal world i should have CarA and CarB. And CarB = CarA relation should be created if CarB exists.
I base this idea on Django 1.5 user extension. Is this clear enough? Or should i do something else?
You could use a property to create the B instance on access if it doesn't exist yet, ie,
class A(models.Model):
title = models.TextField()
# other fields ...
class Meta:
manage = False
#property
def b(self):
if not hasattr(self, "__bcache"):
self.__bcache, created = B.objects.get_or_create(model_a = self)
return self.__bcache
It seems like you're new to both Python and Django so let's explain quickly...
First, the "#property" part: it's a decorator that turns the following function into a computed attribute - IOW you use it as an attribute (myA.b.whatever), and under the hood it turns it into a method call (myA.b().whatever). It's not strictly required here, we would have used an explicit getter (the same method named get_a()) but it's cleaner that way.
Then our method implementation: obviously we don't want to hit the database each time someone looks up A.b, so
first we check if an attribute named __bcache ("b" "cache") is set on the current instance.
if not, we call B.objects.get_or_create(a_model=self) which will either retrieve the existing B instance for this A instance or create one if none exists yet and we store this B instance as self.__bcache so next call will retrieve it directly from __bcache instead of hitting the database.
and finally we return self.__bcache that is now garanteed to exists and point to the related B instance.

Django model operating on a queryset

I'm new to Django and somewhat to Python as well. I'm trying to find the idiomatic way to loop over a queryset and set a variable on each model. Basically my model depends on a value from an api, and a model method must multiply one of it's attribs by this api value to get an up-to-date correct value.
At the moment I am doing it in the view and it works, but I'm not sure it's the correct way to achieve what I want. I have to replicate this looping elsewhere.
Is there a way I can encapsulate the looping logic into a queryset method so it can be used in multiple places?
NOTE: the variable I am setting on each model instance is just a regular attribute, not saved to db. I just need to be able to set that variable, not save it.
I have this atm (I am using django-rest-framework):
class FooViewSet(viewsets.ModelViewSet):
model = Foo
serializer_class = FooSerializer
bar = # some call to an api
def get_queryset(self):
# Dynamically set the bar variable on each instance!
foos = Foo.objects.filter(baz__pk=1).order_by('date')
for item in foos:
item.needs_bar = self.bar
return items
I would think something like so would be better:
def get_queryset(self):
bar = # some call to an api
# Dynamically set the bar variable on each instance!
return Foo.objects.filter(baz__pk=1).order_by('date').set_bar(bar)
I'm thinking the api hit should be in the controller and then injected to instances of the model, but I'm not sure how you do this. I've been looking around querysets and managers but still can't figure it out nor decided if it's the best method to achieve what I want.
Can anyone suggest the correct way to model this with django?
Thanks.
You can set some new properties on queryset items, but they will not update database (will be saved just in local namespace). I suppose that you want to recalculate field of your model multiplying it by some value:
class Foo(models.Model):
calculated_field = models.BigIntegerField(default=0)
def save(self, *args, **kwargs):
if self.pk is not None: # it's not a new record
foo = kwargs.get('foo')
if foo:
self.calculated_field = self.calculated_field * int(foo)
super(Foo, self).save(*args, **kwargs) # Call the "real" save() method.
def get_queryset(self):
bar = # some call to an api
# Dynamically set the bar variable on each instance!
foos = Foo.objects.filter(baz__pk=1).order_by('date')
for item in foos:
item.save(foo=bar)
# return updated data
return Foo.objects.filter(baz__pk=1).order_by('date')
At some point you might need to use transactions if you will run this code simultaneously.

Django model fields: reference to self on default keyword

I've been having problems to understand this and to come up with a way of doing a reference to self inside the default keyword of a model field:
Here is what I have:
class Bank(models.Model):
number = models.CharField(max_length=10)
class Account(models.Model):
bank = models.ForeignKey(Bank, related_name="accounts")
number = models.CharField(max_length=20)
created = models.DateTimeField(auto_now_add=True)
creator = models.ForeignKey(User)
# This is the guy
special_code = models.CharField(max_length=30, default='%s-%s' % (self.number, self.bank.number))
So I'm trying to access self inside the class definition, which seems to not work out because python doesn't know where self is since its not an object yet.
I've tried different things like:
special_code = models.CharField(max_length=30, default='%s-%s' % (number, bank.number))
But in this case it doesn't recognize bank.number because bank its only a property with models.ForeignKey.
I've tried also using a method inside the Account class:
def bank_number(self):
return self.bank.number
and then:
special_code = models.CharField(max_length=30, default='%s-%s' % (number, bank_number()))
That was kinda dumb because it still needs self.
Is there a way I can do this?
I need it to store the number inside the database, so using a method like this wont help:
def special_number(self):
return '%s-%s' % (self.number, self.bank.number)
I don't think there's any way to access self in the default callable. There's a couple of other approaches to set your field's value:
If you don't want the user to be able to change the value, override the model's save method and set it there.
If the default is just a suggestion, and you do want to allow the user to change it, then override the model form's __init__ method, then you can access self.instance and change set the field's initial value.
Instead of specifying a default for the field you probably want to override the save() method and populate the field right before storing the object in the database. The save() method also has access to self. Here is an example in the docs for that:
https://docs.djangoproject.com/en/dev/topics/db/models/#overriding-model-methods
As already answered, override the save() method of your model to assign a value to special_code. The default option of a field is not meant to depend on other fields of the model, so this will not work.
Also, have a look at the editable option, if you don't want the field to be edited.
special_code = models.CharField(max_length=30, editable=False)
Will prevent the field to be rendered in ModelForms you create from the model.

How to manipulate form fields in Django dynamically within ModelAdmin?

I have a field (slug) that is "required" in the model, but want to change the field in the ModelAdmin class to be optional. If the user doesn't fill it in, it is automatically filled in by another field (name).
class SomeModel(model.Model):
name = model.CharField(max_length=255)
slug = model.SlugField(unique=True, max_length=255)
I tried to do this various ways, such as overriding get_form() within ModelAdmin or using the ModelForm class and specifying the form specifically.
class SomeModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(self.__class__, self).get_form(request, obj, **kwargs)
form.slug.required = False
return form
However, neither solution worked for me. Beyond manually creating the form, is there any other quicker solution?
I have a lot of these forms, and doing it by hand might be tedious and hard to maintain.
Found this page through Google when wrestling with the same problem myself. The following will also work in the ModelAdmin:
def get_form(self, *args, **kwargs):
form = super(SomeModelAdmin, self).get_form(*args, **kwargs)
form.base_fields['slug'].required = False
return form
Subsequent forms created from the updated ModelFormMetaclass will have the slug field unrequired.
This works better in my situation, where I have only a single class in which I need to unrequire the field, and don't need to do any data transformation on save. GoogleDroid's solution is better if you have a lot of classes, or where the data transformations are necessary.
In your get_form method, form.fields['slug'].required should work.
But the proper way to do this is to simply provide a custom ModelForm.
class SomeModelForm(forms.ModelForm):
slug = forms.CharField(required=False)
class SomeModelAdmin(admin.ModelAdmin):
form = SomeModelForm
Incidentally, please don't do super(self.__class__, self). You should always explicitly name the current class when using super, otherwise any subclass that inherits from yours and in turn calls super will break.
Edit form.fields, not forms.fields.
By saying self.__class__, you are explicitly stopping Python from working out the inheritance - because it always refers to the concrete class - ie the bottom of the inheritance tree. But if your method is the middle of that tree, then referring to the concrete class in super is wrong - because you want it to call the next level up from where you are, not one up from the bottom. That's why you should always name the class you're in - in this case, super(SomeModelAdmin, self).
I just wanted to report back in case others might find this useful.
I was never able to in get_form method do form.fields['slug'].required and never figured out why. However, I solved my problem by creating a new form inheriting from ModelForm.
I had to override init() to set self.fields['slug'].required = False after calling the parent constructor, then override clean_slug() to modify the slug field content if required by accessing self.data['slug'].
Hope this helps someone

Categories