I have 2 models: Content and Address. Each content has one address so the models are something like this:
class Content(models.Model):
....
class Address(model.Model):
content = OneToOneField(Content)
....
I now want to adjust the ContentSerializer so that I can pass data for an address in it.
class Content(serializers.ModelSerializer):
address = ContentAddressSerializer()
class Meta:
model = Content
fields = ('address')
def create(self, validated_data):
if "address" in validated_data:
address = validated_data.pop('address')
Address.objects.create(**address)
validated_data['editor'] = self.context['request'].user
return Content.objects.create(**validated_data)
I'm succesfully creating an Address but when the to_represantation method is called, I get an AttributeError. Is there a better and easier way than to override the to_representation method?
Related
I have a django.db.models.Model A whose instances are created in a rest_framework.serializers.ModelSerializer from POST requests.
Depending on the data being sent in the POST, I would like to create one of several other "addon" models, let's say B or C, which I link to the original through a django.db.models.OneToOneField:
from django.db import models
class A(models.Model):
some_field = models.CharField()
class B(models.Model):
a = models.OneToOneField(A, related_name='addon', on_delete=models.CASCADE)
class C(models.Model):
a = models.OneToOneField(A, related_name='addon', on_delete=models.CASCADE)
What I would like to is to have a serializer which validates the incoming data, including some string indicating which addon to use. The serializer then creates the model instance of A and based on this creates the addon model.
I do not want to create a utility field in model A used to determine which addon to use, I would like to create the model directly using the instance of model A and information from the POST itself.
At the same time when accessing the data through a get, I would like to return the original string used to determine which addon to use.
What I have come up with so far:
from rest_framework import serializers
str2model = {'b': B, 'c': C}
class AddonField(serializers.Field):
def to_representation(self, value):
# I completely ignore "value" as no "internal value" is set in "to_internal_value"
myvalue = self.parent.instance.addon
for addon_name, addon_class in str2model.items():
if isinstance(myvalue, addon_class):
return addon_name
def to_internal_value(self, data):
# I create the "internal value" after "A" instance is created, thus here I do nothing?
return data
class ASerializer(serializers.ModelSerializer):
some_field = serializers.CharField()
the_addon = AddonField()
def validate_the_addon(self, value): # here addon is a string
if value in str2model.keys():
return value
def create(self, validated_data):
addon_name = validated_data.pop('the_addon')
addon_class = str2model[addon]
a = super(ASerializer, self).create(validated_data)
addon_class.objects.create(a=a)
return a
class Meta:
model = A
fields = ["some_field", "the_addon"]
When testing this I get:
AttributeError: Got AttributeError when attempting to get a value for field `the_addon` on serializer `ASerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `A` instance.
Original exception text was: 'A' object has no attribute 'the_addon'.
How can I temporarily store the_addon in the serializer until the A instance has been created?
This is how I would typically approach it
# Serializer
class ASerializer(serializers.Serializer):
some_field = serializers.CharField()
addon_b = serializers.CharField(required=False, allow_null=True)
addon_c = serializers.CharField(required=False, allow_null=True)
def create(self, validated_data):
addon_b = validated_data.pop('addon_b')
addon_c = validated_data.pop('addon_c')
a = A.objects.create(some_field=validated_data['some_field'])
if addon_b:
B.objects.create(a=a)
if addon_c:
C.objects.create(a=a)
return a
You can do other validations if necessary.
class TestAPIView01(generics.CreateAPIView):
permission_classes = {}
serializer_class = serializers.ASerializer
queryset = A.objects.all()
Also, look at the related_name on B and C you may want to consider making them different, as that might throw an error in the future. Cheers
I have an API endpoint that return a list of objects of one of my models.
I have a very trivial serializer that currently returns all the model fields.
What I need now is to add to this serializer another field which is calculated by applying a logic on one of the model field and another data structure that is also calculated dynamicly - but should be calculated only once for each call to the API.
What is the way to supply this structure to the serializer for every API call but not for every object the serializer returns?
My ModelViewSet (without the extra field) is quite trivial:
class MyModelViewset(ModelViewSet):
queryset = SomeModel.objects.all()
serializer_class = SomeModelSerializer
Some suggestions on how you can achieve this:
To calculate something only once and use it in the serializer you should add it to the serializer context.
You can create #property in your SomeModel class and it can be exposed via serializer fields as read only. Django has also #cached_property so that value is only calculated once and persist as long as the instance does
You can use SerializerMethodField field if you want to calculate something (you have access the serializer context in there).
Just a quick example on how to use this.
class MyModelViewset(ModelViewSet):
queryset = SomeModel.objects.all()
serializer_class = SomeModelSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context['my_value'] = [] # calculate something here, you have access to self.request
return context
class SomeModelSerializer(serializers.ModelSerializer):
my_data = serializers.SerializerMethodField()
class Meta:
model = SomeModel
def get_my_data(self, obj):
# you have access to self.context['my_value']
# you have access to obj.some_property
return my_data
I can't get Django to serialize the AL_NODE as a modelserializer. Is it possible to serialize AL_NODEs?
Here is my code:
class UserSecuritySelectionModelSerializers(serializers.ModelSerializer):
class Meta:
model = UserSecuritySelectionModel()
fields = ('hasChildNode', 'classificationNames', 'tgtWeight','currWeight','SSM','ext_model_id')
Here is a sample of the data and how it is structured in the database:
Code in my views.py
if request.is_ajax() and id is not None:
rootNode = UserSecuritySelectionModel.objects.get(SSM_id=id, classificationNameNode__isnull=True)
if not rootNode.is_root():
node = rootNode.get_root()
data = serializers.serialize('json', node, use_natural_foreign_keys=True)
return JsonResponse(data, safe=False)
userSelectionModelSerializer = UserSecuritySelectionModelSerializers(rootNode)
#data = serializers.serialize('json', [rootNode], use_natural_foreign_keys=True)
return JsonResponse (userSelectionModelSerializer.data, status=201, safe=False)
You say it's not working but you haven't included any details about the error you are getting. This makes everything here a guess.
You should just be setting the model reference to a class, not actually creating an instance of the model
model = UserSecuritySelectionModel()
# should be
class Meta:
model = UserSecuritySelectionModel
Next, I think you should explicitly pass in the instance just to be safe. It's not required, but it makes your intention clear:
UserSecuritySelectionModelSerializers(instance=rootNode)
Third, just return a standard Response, not a JsonResponse, DRF will do the content negotiation for you. That is why you are using it.
return Response(MyLongSerializer(instance=root).data)
Finally, is there any reason for sending a 201? You do not appear to be creating anything in the view. If you are, then send it like this:
return Response(..., status=HTTP_201_CREATED)
DRF will use the editable=False on a field to default the Serializer to read-only. This is a very helpful / safe default that I take advantage of (ie I won't forget to set the Serializer to read-only). That being said once I have set editable=False is there any way to then force the Django admin to allow editing one of those fields?
Presumably the admin is a super user and I do want him to be able to change the fields value but fore safety I want the default Serializer logic to be read only.
UPDATE
I don't actually need to be able to edit the field as much as "set-it" when I create the object.
You are going about this the wrong way.
Your models should be the most pure implementation of the things you are modelling. If something about a model is fixed (for example a creation date) it shouldn't be editable in the model, if its mutable, then leave as editable in the model.
Otherwise, in the future you (or someone else) might be stuck wondering why a field which is set to editable=False is some how being changed. Especially as the documentation states:
If False, the field will not be displayed in the admin or any other ModelForm. They are also skipped during model validation.
If you have one view in which it shouldn't be editable (such as in the API), then override it there.
If you have multiple serilaizers for a model, instead make an abstract serializer with a read_only_fields set and then subclass that. For example:
class AbstractFooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
read_only_fields = ('bar',)
class MainFooSerializer(AbstractFooSerializer):
pass
class DifferentFooSerializer(AbstractFooSerializer):
pass
If you really, really want to use editable=False, but allow the item to be edited in the Admin site only on creation you have an up hill battle.
Probably the best approach would be to reimplement the AdminForm you are using for the Admin
So instead of:
class FooAdmin(admin.ModelAdmin):
Use:
class FooAdmin(admin.ModelAdmin):
form = MySpecialForm
Then declare the form:
class MySpecialForm(forms.Model):
def __init__(self, *args, **kwargs):
self.is_new = False
if kwargs.get('instance',None) is None:
# There is no instance, thus its a new item
self.is_new = True
self.fields['one_time_field'] = forms.CharField() # Or what have you.
super(MySpecialForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
instance = super(MySpecialForm, self).save(commit)
if self.is_new:
instance.your_one_time_only_field = self.one_time_field
instance.save()
return instance
Note: you will need to manually add a field and save each readonly field that you want to do this for. This may or may not be 100% functional.
For those who want to allow editing of a non-editabled field only during creation (no instance.pk, yet):
# models.py
class Entity(Model):
name = CharField(max_length=200, unique=True, null=False, blank=False, editable=False)
# admin.py
#register(Entity)
class EntityAdmin(ModelAdmin):
def get_readonly_fields(self, request, obj=None):
if obj: # This is the case when obj is already created i.e. it's an edit
return ['id', 'name']
else:
return []
# this override prevents that the new_name field shows up in the change form if it's not a creation
def get_form(self, request, obj=None, **kwargs):
orig_self_form = self.form
if not obj:
self.form = CreateEntityForm
result = super().get_form(request, obj=obj, **kwargs)
self.form = orig_self_form
return result
# forms.py
class CreateEntityForm(ModelForm):
new_name = CharField(max_length=200, min_length=2, label='Name', required=True)
def clean_new_name(self):
code = self.cleaned_data['new_name']
# validate uniqueness - if you need
exists = Entity.objects.filter(name=code).first()
if exists:
raise ValidationError('Entity with this name already exists: {}', exists)
return name
def save(self, commit=True):
if self.instance.pk:
raise NotImplementedError('Editing of existing Entity is not allowed!')
self.instance.name = self.cleaned_data['new_name'].upper()
return super().save(commit)
class Meta:
model = Entity
fields = ['new_name']
exclude = ['id', 'name']
I am working with google app engine,
I have 2 classes namely A and B and one db model say DB,
I have processed object of DB model in class A, and i want
to send the same object in class B, How can i do this?
Here is my code,
class Candidate(db.Model):
name = db.StringProperty()
lastname = db.StringProperty()
email = db.StringProperty()
mobno = db.StringProperty()
class A(webapp2.RequestHandler):
def post(self):
currCandidate = Candidate(key_name=self.request.get('email'))
currCandidate.name = self.request.get('name')
currCandidate.lastname=self.request.get('lname')
currCandidate.email=self.request.get('email')
currCandidate.mobno=self.request.get('mobno')
class B(webapp2.RequestHandler):
def get(self):
# currCandidate
# want to access currCandidate object here,
# currCandidate is from class A
I want to access the currCandidate object from class B.
Your 'A' handler is a POST, and gets a Candidate object via keyname, which is the email request parameter. Can you not use the same mechanism for the 'B' handler? This is a GET so you will need to pass the email in as a URL parameter. For example https://yourapp.appspot.com/b?email=someemail#domain.com.
You can then pick it up again with the same code you are using in the POST - currCandidate = Candidate(key_name=self.request.get('email')).
since you are creating a web app,you can store the object in session variable and access it in other class.In java this is done by serializing the object and storing it in session variable