I'm looking for the most efficient way to implement this kind of mechanism in Django model.
Let's assume a situation, where there are 2 very simple models:
class FKModel(models.Model):
value = BooleanField()
class AModel(models.Model):
fk = models.ForeignKey(FKModel)
a_value = models.CharField(max_length=150)
def clean(self, *args, **kwargs):
# the line below is incorrect
if not self.fk.value: # <--- how to do this in a proper way?
raise ValidationError('FKModel value is False')
super(AModel, self).clean(*args, **kwargs)
def save(self, *args, **kwargs):
self.full_clean()
super(AModel, self).save(*args, **kwargs)
I know, that I can do somethink like FKModel.objects.all()/.get(), but I don't think it is the best solution (as it requires additional requests to database).
I am not sure what you try to do in your clean() method, but I assume you are trying to constrain a not null condition for the foreign key. All fields are not null constrained by default, and you have to set null=False and blank=False if you want the field to accept nulls:
class AModel(models.Model):
fk = models.ForeignKey(FKModel, null=True, blank=True)
a_value = models.CharField(max_length=150)
If you want to constrain a not null condition for a field by hand, you should do it like this:
class FKModel(models.Model):
value = BooleanField()
class AModel(models.Model):
fk = models.ForeignKey(FKModel)
a_value = models.CharField(max_length=150)
def clean(self, *args, **kwargs):
# the line below is correct
if self.fk is None: # <--- this is the proper way?
raise ValidationError('FKModel value is False')
super(AModel, self).clean(*args, **kwargs)
def save(self, *args, **kwargs):
self.full_clean()
super(AModel, self).save(*args, **kwargs)
For retrieving database records and its related records, you use prefetch_related, and you get your record and its related records in one single database hit:
AModel.objects.all().prefetch_related('fk')
Related
the below form + view works fine
forms.py
class FormGlobalSettings(forms.Form):
global_font_family = forms.TypedChoiceField(required=False, label='Font Family', choices=choices_font_family, empty_value=None)
views.py
def main_view(request):
if request.method != 'POST':
form_global_settings = FormGlobalSettings()
else:
form_global_settings = FormGlobalSettings(data=request.POST)
if all([form_global_settings.is_valid()]):
cleaned_data = form_global_settings.cleaned_data
nested_data = {"fontFamily": cleaned_data3['global_font_family']}
return JsonResponse(nested_data)
return render(request, 'pbi_theme_app/index.html', {'form_global_settings': form_global_settings})
Output is:
{"fontFamily": "Arial"}
However, what I am trying to achieve is, that sometimes the POST request is blank/empty/null, as the user doesn't want to have any value in this field. The choices for the global_font_family are set up in this manner:
choices_font_family = [(None, ""), ("Arial", "Arial"), etc..]
Meaning that the if the user leaves the field empty, it results into None. This results into having it in the json as null:
{"fontFamily": null}
Now what I am trying to achieve, and because I have hardcoded "fontFamily" into the jsonresponse, I want the whole key, value pair to be gone if the user decides to have the field empty, meaning that the whole key, value pair "fontFamily: null is gone from the jsonresponse.
To summarize: I need to somehow make the key in the json dynamic, and when the POST request is empty, I want to leave the whole key, value pair from getting inputed into the json.
The intended behaviour is seen on the following webpage, when you download the theme and you didnt input anything it leaves the whole json code empty:
https://powerbi.tips/tools/report-theme-generator-v3/
Thank you :)
First of all, you don't need to include none in the choice list. You can simply declare null=True in your model.py class fields.
You can achieve the intended behavior by overwriting the intended field in your model.py class before saving the input
let assume I have the following class.
choices_font_family = [
("c1", "choice1"),
("c2", "choice2"),
("c3", "choice3"),
("c4", "choice4")
]
class Post(models.Model):
theme_type = models.CharField(null=True, max_length=255, verbose_name="some text", choices=choices_font_family )
....
....
def save(self, force_insert=False, force_update=False, *args, **kwargs):
if theme_type is None:
theme_type = '<some font>' # overwriting none to whatever you want it to be
super().save(force_insert, force_update, *args, **kwargs)
EDIT
forms.py
from django import forms
from .models import FormGlobalSettings
class UserdataModelForm(forms.ModelForm):
class Meta:
model = FormGlobalSettings
def clean(self):
return self.clean()
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(UserdataModelForm, self).__init__(*args, **kwargs)
models.py
from django.db import models
choices_font_family = [
("c1", "choice1"),
("c2", "choice2"),
("c3", "choice3"),
("c4", "choice4")
]
class FormGlobalSettings(models.Model):
theme_type = models.CharField(null=True, max_length=255, verbose_name="some text", choices=choices_font_family )
....
....
def save(self, force_insert=False, force_update=False, *args, **kwargs):
if theme_type is None:
theme_type = '<some font>' # overwriting none to whatever you want it to be
super().save(force_insert, force_update, *args, **kwargs)
Here is a link to Django docs on saving and overwriting save. And here is an example from Django docs on how to use forms and models.
If this solves your problem don't forget to accept this as the correct answer.
I am trying to set the initial value of a field on a form. The field is not part of the model, but when I try and set it to a value the field is blank. From my research it could be because the form is "bound" which makes some sense to me, but in this case the field is not part of the model.
My form:
#Form for editing profile
class CatForm(forms.ModelForm):
pictureid = forms.CharField()
class Meta:
model = Cat
fields = ['name']
def __init__(self, *args, **kwargs):
picid = kwargs.pop("pictureid")
print(picid)
super(CatForm, self).__init__(*args, **kwargs)
self.fields['pictureid'] = forms.CharField(initial=picid, required=False)
The model:
class Cat(models.Model):
name = models.CharField(max_length=34,null=False)
From the view it is called like this:
catform = CatForm(request.POST, pictureid=instance.id)
I was expecting it to set the field to the value of the initial attribute, but it doesn't. I have tried testing it by directly adding a string, but doesn't set.
This is what seems to be working for me:
class CatForm(forms.ModelForm):
class Meta:
model = Cat
fields = ['name']
def __init__(self, *args, **kwargs):
picid = kwargs.pop("pictureid")
super(CatForm, self).__init__(*args, **kwargs)
self.fields['pictureid'] = forms.CharField(initial=picid)
I also needed to drop the "request.POST" from the call to this when initiating the form.
If you want to render the pictureid in GET request, then you can try like this:
catform = CatForm(initial={'pictureid': instance.id})
For GET request, you don't need to override the __init__ method.
But, if you want to use the Catform in POST request, to use the value of pictureid somewhere else(lets say in save method), then you will need to override __init__ method here.
class CatForm(forms.ModelForm):
pictureid = forms.CharField()
class Meta:
model = Cat
fields = ['name']
def __init__(self, *args, **kwargs):
picid = kwargs.pop("pictureid")
print(picid)
super(CatForm, self).__init__(*args, **kwargs)
self.pictureid = picid
def save(self, *args, **kwargs):
print(self.pictureid) # if you want to use it in save method
return super().save(*args, **kwargs)
I've got a Django models:
class Item_received_log(models.Model):
name = models.CharField(max_length=250)
quantity = models.FloatField(default=1)
class Inventory (models.Model):
name = models.CharField(max_length=250)
quantity = models.FloatField(default=1)
I would like to update Inventory.quantity each time new item to Item_received_log is posted with matching name. I am not sure if it is right but I've decided to override save method of Item_received_log class so it updates Inventory list upon saving:
def save(self, *args, **kwargs):
obj, created = Inventory.objects.update_or_create(
name=self.name,
defaults = {'quantity':(quantity + self.quantity)})
super(Item_received_log, self).save(*args, **kwargs)
And in returns:
NameError at /admin/accountable_persons/item_received_log/17/change/
global name 'quantity' is not defined
How can I resolve my issue or come up with better solution?
Would have been a lot easier if we could simply throw in an F() expression into the default part of update_or_create to do all the magic, but the issue requesting this feature is still open.
You can, however, use a more verbose approach for now:
from django.db.models import F
def save(self, *args, **kwargs):
obj, created = Inventory.objects.get_or_create(name=self.name)
obj.quantity = F('quantity') + self.quantity
obj.save()
super(Item_received_log, self).save(*args, **kwargs)
I am trying to override the save method on a model in order to generate a unique, second auto-incrementing id.
I create my class and override the save() method, but for some reason it is erroring out with the following error:
TypeError: %d format: a number is required, not NoneType
Here's the code:
class Person(models.Model):
target = models.OneToOneField(Target)
person = models.OneToOneField(User)
gender = models.CharField(max_length=1)
gender_name = models.CharField(max_length=100)
person_id = models.CharField(max_length=100)
def save(self, *args, **kwargs):
self.person_id = "%07d" % self.id
super(Person, self).save(*args, **kwargs)
Is it because I didn't pass an id parameter and it hasn't saved yet? Is there anyway to generate a value from the id?
Safest and easiest way to achieve what you want is to use a post_save signal because it is fired right after save is called, but before the transaction is committed to the database.
from django.dispatch import receiver
from django.db.models.signals import post_save
#receiver(post_save, sender=Person)
def set_person_id(sender, instance, created, **kwargs):
if created:
instance.person_id = "%07d" % instance.id
instance.save()
Yes, self.id will be Nonein some cases, and then the assignment will fail.
However you cannot just the assignment and the call to super, as suggested in the comments, because then you wouldn't be persisting the assignment to the database layer.
You need to check whether the model has an id and then proceed differently:
def save(self, *args, **kwargs):
if not self.id: # Upon instance creation
super(Person, self).save(*args, **kwargs) # Acquire an ID
self.person_id = "%07d" % self.id # Set the person_id
return super(Person, self).save(*args, **kwargs)
This issues two save operations to the database. You will want to wrap them in a transaction to make sure your database receives these two fields simultaneously.
from django.db import IntegrityError, transaction
class Person(models.Model):
target = models.OneToOneField(Target)
person = models.OneToOneField(User)
gender = models.CharField(max_length=1)
gender_name = models.CharField(max_length=100)
person_id = models.CharField(max_length=100)
def create_person_id(self):
if not self.id: # Upon instance creation
super(Person, self).save(*args, **kwargs) # Acquire an ID
self.person_id = "%07d" % self.id
def save(self, *args, **kwargs):
try:
with transaction.atomic():
self.create_person_id
return super(Person, self).save(*args,**kwargs)
except IntegrityError:
raise # or deal with the error
I agree that signals might be the better option, if not, try using pk instead of id.
class Person(models.Model):
# [ . . . ]
def save(self, *args, **kwargs):
self.person_id = "%07d" % self.pk
super(Person, self).save(*args, **kwargs)
I have model with field rules = models.ManyToManyField(blank=True, null=True) and aggregation = models.BooleanField(default=False).
And I want to make aggregation=False in case if rules is empty. In other words, if there are no any rule entered or already stored in database, aggregation field should be always == False.
def save(self, *args, **kwargs):
if not self.rules:
self.aggregation = False
super(MyModel, self).save(*args, **kwargs)
Is this what you mean?
Edit: I see that you might want to check the database also. No problem:
def save(self, *args, **kwargs):
models = MyModel.objects.filter(rules__isnull=False).exists()
if not self.rules and not models:
self.aggregation = False
super(MyModel, self).save(*args, **kwargs)