Built-In callable as a default argument for a Django Field - django

I have a JSONField that I need to apply a default dictionary to. As per the documentation, I am avoiding passing the mutable dictionary to the default field. This is done by instead passing the copy method to the default argument like such:
default_dict = {'some_key': 'some value'}
class MyModel(models.Model):
my_field = models.JSONField(default=default_dict.copy)
When applying makemigrations, this is failing because of the following condition in django.db.migrations.serializer.FunctionTypeSerializer:
if self.value.__module__ is None:
raise ValueError("Cannot serialize function %r: No module" % self.value)
I can get around this by defining a callable that returns a copy, but I think this is adding unnecessary syntax and makes it harder to read:
class ADict(dict):
def __call__(self):
return self.copy()
default_dict = ADict({'some_key': 'some value'})
class MyModel(models.Model):
my_field = models.JSONField(default=default_dict)
Is there a way to pass a built-in objects method as the default value for a Django field?

You can't do this since it basically needs to be a named function, whereas default_dict.copy is an "anonymous" function.
You can however make a named function like:
default_dict = {'some_key': 'some value'}
def copy_default_dict():
return default_dict.copy()
class MyModel(models.Model):
my_field = models.JSONField(default=copy_default_dict)
or even simpler:
def copy_default_dict():
return {'some_key': 'some value'}
class MyModel(models.Model):
my_field = models.JSONField(default=copy_default_dict)

Related

Alter the parameter name during serialization

I am having struggles with the alteration on the parameter name during serialization with DRF.
My input would be a JSON with some parameters:
{
"limit": 10,
"type": "group",
[...]
}
and my serializer looks like:
class RankSerializer(serializers.Serializer):
limit = serializers.IntegerField(default=100, min_value=1)
type = serializers.CharField()
def validate_type(self, t):
# validation
But this doesn't sound right. Type is a reserved keyword in Python so I don't want to use it as a parameter name. I'd like to somehow map it to i.e result_type or something like this.
I already tried using the source= parameters as follows:
result_type = serializers.CharField(source='type')
but this doesn't seem to work on non-model inputs.
I cannot rename the parameter on the frontend level.
I'd appreciate any tips regarding this issue. Cheers.
I searched https://www.django-rest-framework.org/api-guide/fields/ of DRF docs but was unable to find a viable solution. So I subclassed the Serializer class provided by DRF and created a CustomSerializer. In that, I subclassed the run_validation method. In that, before calling super().run_validation(), I access the initial_data passed and change it to the desired mapping as you mentioned. This information of field mappings I store in the Meta class nested inside RankSerializer. So for example, you have fields F1, F2, F3(in JSON data, for example) whose values you want to populate in fields named M1, M2, M3, you just have to write in the Meta class in the field_mappings dictionary the following :
field_mappings = {
'M1': 'F1',
'M2':'F2',
'M3': 'F3'
}
The other fields will function normally.
Here is the code
import rest_framework.serializers as serializers
from .models import Rank
from rest_framework.fields import empty
class CustomSerializer(serializers.Serializer):
def run_validation(self, data=empty):
for (field, mapping) in self.Meta.field_mappings.items():
data[field] = data[mapping]
del data[mapping]
return super().run_validation(data=data)
class RankSerializer(CustomSerializer):
limit = serializers.IntegerField(default=100, min_value=1)
result_type = serializers.CharField(required=True)
class Meta:
field_mappings = {
'result_type': 'type'
}
def create(self, validated_data):
print(validated_data)
def update(self, instance, validated_data):
print(validated_data)
'''
Please run the code below in the InteractiveShell to verify the result
from myapp.serializers import RankSerializer
serializer = RankSerializer(data={"limit":15, "type":"Hello"})
serializer.is_valid()
'''
After running the code in the multi line comments to verify the result here is the snapshot
The workaround I found was to modify the parameter name in the validate method as follows:
def validate(self, data):
data = super(RankSerializer, self).validate(data)
data['result_type'] = self.validate_result_type(self.initial_data.get('type'))
return data
Not sure if this is the cleanest way to do it, but I prefer it over #punter147 answer.

Django function as Charfield Choices

Consider the following models with the following fields:
Powers:
class Power(models.Model):
...
half = models.BooleanField()
micro = models.BooleanField()
UseP:
class UseP(models.Model):
...
power_policy = models.ForeignKey(Power, on_delete=models.CASCADE)
def use_types():
TYPES = []
TYPES.append(("F", "Full Use"))
if power_policy.half:
TYPES.append(("H", "Half Use"))
if power_policy.micro:
TYPES.append(("M", "Micro Use"))
return tuple(TYPES)
use_type = models.CharField(max_length=1, choices=use_types())
The function doesn't run. As you can see, when I try without the "self" arguments, it says that power_policy is not defined. If I do it like self.power_policy, it recognizes the power policy but then when I got and call the function like use_type = models.CharField(max_length=1, choices=self.use_types()), it says that the self keyword is not defined in this line.
I think the code explains it all, but in case it doesn't I want to provide choices to my user after they choose the Power, according to the power option. For which, I created a function but it doesn't really seem to work. What am I doing wrong or is it not even possible like this?
Thank You for your time,
If you create a method in your class, which uses fields from that class - you have to pass self argument to your method.
Change your method to: def use_types(self): then you can use your fields like self.power_policy.
You cannot use your field use_type like this. If you want to set use_type based on power_policy field you can do this in your save method like so:
def save(*args, **kwargs):
if self.power_policy and self.power_policy.half:
self.use_type = 'H'
elif self.power_policy and self.power_policy.micro:
self.use_type = 'M'
return super(UseP, self).save(*args, **kwargs)

Self M2M with django

I am trying to create create a M2M value on self within the same model. I can update the name field fine. However, I keep getting the TypeError when I update the M2M (supertag) field.
models.py
class Tag(models.Model):
name = models.CharField("Name", max_length=5000, blank=True)
supertag = models.ManyToManyField('self', blank=True)
serializers.py
supe = tag.all()
print(supe)
# returns [<Tag: XYZ>, <Tag: 123>]
for y in supe:
# import pdb; pdb.set_trace()
tag = Tag.objects.update(supertag__pk=y.pk)
tag.save()
error:
TypeError: 'supertag__pk' is an invalid keyword argument for this function
I also tried just tag = Tag.objects.update(supertag=supe) which gave the same error
supe is a queryset, it doesn't have a pk attribute.
Also, you are using same name for different variables. tag has already been assigned.
supe = tag.all()
When assigning tag to a new object would affect the working of the for loop, which is based on the former tag variable.
tag = Tag.objects.get_or_create(supertag__pk=supe.pk)
You can't do this.
EDIT
The function get_or_create actually returns tuple, an object and a boolean flag.
The boolean flag specifies whether the object was created or not.
So, the logic you were implementing was wrong. As we discussed,
You could do something like this,
for x in supe:
if x.taglevel == 1:
for value in supe:
x.tag.add(value)
x.save()
else:
#your next logic
print("No level 1")

Override get_FIELD_display method in Django model

Let's say I have a field called field in my model, with a choices parameter defining the values to be returned by the get_field_display method.
I need the get_field_display method to return a different value based on another field. Is there any way to override get_field_display?
This doesn't work:
def get_field_display(self):
if self.other_field == 1:
return 'Other value'
return super.get_field_display(self)
You can't call super because the function is defined not by the parent class but by the ModelBase metaclass. Try with this:
def get_field_display(self):
if self.other_field == 1:
value = 'Other value'
else:
field_object = self._meta.get_field('field')
value = self._get_FIELD_display(field_object)
return value
What you can do is to create a different function in the same model, then monkey patch it. For example, in admin.py you may do something like:
ClassName.get_field_display = ClassName.get_patched_field_display
It's not very 'nice' but it works for me.

Django : Validate data by querying the database in a model form (using custom clean method)

I am trying to create a custom cleaning method which look in the db if the value of one specific data exists already and if yes raises an error.
I'm using a model form of a class (subsystem) who is inheriting from an other class (project).
I want to check if the sybsystem already exists or not when i try to add a new one in a form.
I get project name in my view function.
class SubsytemForm(forms.ModelForm):
class Meta:
model = Subsystem
exclude = ('project_name')
def clean(self,project_name):
cleaned_data = super(SubsytemForm, self).clean(self,project_name)
form_subsystem_name = cleaned_data.get("subsystem_name")
Subsystem.objects.filter(project__project_name=project_name)
subsystem_objects=Subsystem.objects.filter(project__project_name=project_name)
nb_subsystem = subsystem_objects.count()
for i in range (nb_subsystem):
if (subsystem_objects[i].subsystem_name==form_subsystem_name):
msg = u"Subsystem already existing"
self._errors["subsystem_name"] = self.error_class([msg])
# These fields are no longer valid. Remove them from the
# cleaned data.
del cleaned_data["subsystem_name"]
return cleaned_data
My view function :
def addform(request,project_name):
if form.is_valid():
form=form.save(commit=False)
form.project_id=Project.objects.get(project_name=project_name).id
form.clean(form,project_name)
form.save()
This is not working and i don't know how to do.
I have the error : clean() takes exactly 2 arguments (1 given)
My model :
class Project(models.Model):
project_name = models.CharField("Project name", max_length=20)
Class Subsystem(models.Model):
subsystem_name = models.Charfield("Subsystem name", max_length=20)
projects = models.ForeignKey(Project)
There are quite a few things wrong with this code.
Firstly, you're not supposed to call clean explicitly. Django does it for you automatically when you call form.is_valid(). And because it's done automatically, you can't pass extra arguments. You need to pass the argument in when you instantiate the form, and keep it as an instance variable which your clean code can reference.
Secondly, the code is actually only validating a single field. So it should be done in a specific clean_fieldname method - ie clean_subsystem_name. That avoids the need for mucking about with _errors and deleting the unwanted data at the end.
Thirdly, if you ever find yourself getting a count of something, iterating through a range, then using that index to point back into the original list, you're doing it wrong. In Python, you should always iterate through the actual thing - in this case, the queryset - that you're interested in. However, in this case that is irrelevant anyway as you should query for the actual name directly in the database and check if it exists, rather than iterating through checking for matches.
So, putting it all together:
class SubsytemForm(forms.ModelForm):
class Meta:
model = Subsystem
exclude = ('project_name')
def __init__(self, *args, **kwargs):
self.project_name = kwargs.pop('project_name', None)
super(SubsystemForm, self).__init__(*args, **kwargs)
def clean_subsystem_name(self):
form_subsystem_name = self.cleaned_data.get("subsystem_name")
existing = Subsystem.objects.filter(
project__project_name=self.project_name,
subsytem_name=form_subsystem_name
).exists()
if existing:
raise forms.ValidationError(u"Subsystem already existing")
return form_subsystem_name
When you do form=form.save(commit=False) you store a Subsystem instance in the variable form but the clean method is defined in SubsystemForm. Isn't it?