django-rest-framework access field inside serializer - django

So I have a model like this
class DataSheet(BaseModel):
"""
Represents a single dataSheet.
dataSheets have their own model at the core. Model data is added to
the dataSheets in the form of separate records.
"""
class Meta:
verbose_name = 'datasheet'
verbose_name_plural = 'datasheets'
ordering = ['position', 'cluster']
required_db_features = {
'supports_deferrable_unique_constraints',
}
constraints = [
models.UniqueConstraint(
fields=['position', 'cluster'],
name='deferrable_unique_datasheet_position',
deferrable=models.Deferrable.DEFERRED
)
]
def __str__(self):
return self.name
objects = managers.DataSheetsManager()
positions = managers.PositionalManager()
position = models.PositiveSmallIntegerField(db_index=True, editable=True)
name = models.CharField(max_length=100, validators=[MinLengthValidator(2)], db_index=True)
description = models.CharField(max_length=1024, null=True, blank=True, db_index=True)
owner = models.ForeignKey('api_backend.Member', on_delete=models.CASCADE, db_index=True, editable=False)
fields = models.ManyToManyField('api_backend.Field')
overwrites = models.ManyToManyField('api_backend.RoleOverwrite')
parent = models.ForeignKey('api_backend.Category', on_delete=models.CASCADE, null=True, blank=True)
cluster = models.ForeignKey('api_backend.Cluster', on_delete=models.CASCADE, editable=False)
REQUIRED_FIELDS = [name, owner, cluster]
and a serializer like this
class DataSheetSerializer(serializers.ModelSerializer):
"""
A serialized DataSheet Object.
Datasheets have their own:
- array of fields
- array of role-overwrites
"""
def get_fields(self):
fields = super(DataSheetSerializer, self).get_fields()
fields['parent'].queryset = self.cluster.categories.all()
return fields
class Meta:
model = DataSheet
read_only_fields = ['position']
fields = '__all__'
# need to make sure that the parent category of the datasheet
# belongs to the datasheet's cluster only.
fields = partial.PartialFieldSerializer(many=True, read_only=True)
overwrites = partial.PartialOverWriteSerializer(many=True, read_only=True)
the thing is, I want to access the serializer model's cluster field inside of the get_fields method. However, I couldn't do the same. Can someone help me?
I've seen other answers involving initial_data, but that doesn't work here.
fields['parent'].queryset = self.cluster.categories.all()
cluster is an unresolved reference here.

self in get_fields is DataSheetSerializer instance not DataSheet model instance. hence it should not have cluster property. you can not access model DataSheet instance in get_fields as it gets fields from class DataSheet not from its instance. you can validate the field like
class DataSheetSerializer(serializers.ModelSerializer):
# ... other code
def validate(self, data):
parent = data.get('parent')
# check if parent is valid i.e in queryset
# if yes return data
# else raise serializers.validationError

Related

Django DRF - Updating many-to-one model in serializer

My models structure is pretty straightforward: a many to one relationship where a set of Conditions references one Item by the foreign key.
And here we have the corresponding serializers.py:
class ConditionSerializer(serializers.ModelSerializer):
class Meta:
model = Condition
fields = ('condition_id','name', 'left', 'operator', 'right')
class ItemSerializer(serializers.ModelSerializer):
conditions = ConditionSerializer(many=True, read_only=True)
def create(self, validated_data):
conditions_data = validated_data.pop("conditions")
item = Item.objects.create(**validated_data)
for condition_data in conditions_data:
Condition.objects.create(item=item, **condition_data)
return item
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
conditions_data = validated_data.get('conditions', instance.conditions)
for c in conditions_data.all():
print(c.name) # it correctly prints out the value
instance.conditions.set(conditions_data.all()) # Doesn't update the instance
instance.save()
return instance
class Meta:
model = Item
fields = ('item_id', 'name', 'conditions')
The problem is in the update function:
instance.name is correctly updated; instance.conditions.set(conditions_data.all()) is not.
No error is displayed, since the response is 200OK.
I also put the for loop printing of field of the Condition object (name) and it successfully is displayed, showing that the data is correctly read.
I've tried to address the issue removing the read_only=True from the ConditionSerializer, but the situation got worse since I receive the error:
AttributeError: 'list' object has no attribute 'all'
Maybe the issue is related to the foreign key management.
Is there a way to solve this?
UPDATE: here the models
class Item(models.Model):
item_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
created_on = models.DateTimeField(auto_now_add=True, editable=False)
last_updated = models.DateTimeField(auto_now=True, editable=False)
name = models.CharField(max_length=64)
readonly_fields=['item_id','created_on','last_updated']
class Condition(models.Model):
condition_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=128)
left = models.JSONField()
operator = models.CharField(max_length=2)
right = models.JSONField()
item = models.ForeignKey(Item, related_name="conditions", on_delete=models.CASCADE, null=True)
I think you need to set their ids.
class ItemSerializer(serializers.ModelSerializer):
conditions = ConditionSerializer(many=True, read_only=True)
...
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
conditions_data = validated_data.get('conditions', instance.conditions)
condition_ids = []
for c in conditions_data.all():
condition_ids.append(c.id)
instance.conditions.set(condition_ids)
instance.save()
return instance

Django: how to include missing pk field into serializer when updating nested object?

I have a serializer in my Django app that is meant for updating a nested object. Updating works, but I'm facing another problem: I can't delete objects that are not in validated_data['events] because I don't have the id to be compared with my instance id's.
For reference, these are my Models:
class Plan(models.Model):
planId = models.CharField(primary_key=True, max_length=100, unique=True)
name = models.CharField(max_length=200)
class PlanEvent(models.Model):
plan = models.ForeignKey(Plan, on_delete=models.CASCADE)
id = models.CharField(primary_key=True, max_length=100, unique=True, blank=False, null=False)
done = models.BooleanField()
title = models.CharField(max_length=100, blank=True)
This is my PlanEventUpdateSerializer:
class PlanEventUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = PlanEvent
fields = ('done', 'title')
Is there some way to include the id, so I could compare the id's like this in my update method:
class PlanUpdateSerializer(serializers.ModelSerializer):
events = PlanEventUpdateSerializer(many=True)
class Meta:
model = Plan
fields = ('name',)
....
def update(self, instance, validated_data):
events_validated_data = validated_data.pop('events')
events = (instance.events.all())
events = list(events)
event_ids = [item['id'] for item in events_validated_data]
for event in events:
if event.id not in event_ids:
event.delete()
I found a solution. I defined the id as a optional field in the serializer and then I was able to include it in the fields. Sending POST and PUT requests works now and I'm also able to delete objects when updating:
class PlanEventUpdateSerializer(serializers.ModelSerializer):
id = serializers.CharField(source='pk', required=False)
class Meta:
model = PlanEvent
fields = ('id', 'done', 'title')

django checkbox select multiple models

Hi I have the following django model:
class Issue(models.Model):
title = models.CharField(max_length=200)
date = models.DateTimeField(auto_now=True)
assignee = models.ForeignKey(User, on_delete=models.CASCADE, related_name='assignee')
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='owner', null=True, blank=True)
description = models.TextField()
state = models.IntegerField(choices=STATUS_CHOICES, default=1)
priority = models.IntegerField(choices=RELEVANCE_CHOICES, default=2)
expired_date = models.DateField(auto_now=False, null=True, blank=True)
and a form which allow a user to create an Issue instance:
class IssueForm(forms.ModelForm):
class Meta:
model = Issue
fields = ('title', 'description', 'assignee', 'state', 'priority', 'expired_date')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].label = "Titolo"
self.fields['description'].label = "Descrizione"
self.fields['state'].label = "Stato"
self.fields['priority'].label = "Priorità"
self.fields['expired_date'].label = "Termine"
self.fields['expired_date'].widget.attrs.update({'class': 'datepicker'})
self.fields['assignee'] = forms.MultipleChoiceField(
choices=self.fields['assignee'].choices,
widget=forms.CheckboxSelectMultiple,
label=("Assegnatario")
)
def clean(self):
cleaned_data = super().clean()
user_id = [i for i in cleaned_data['assignee']]
cleaned_data['assignee'] = [User.objects.get(id=i) for i in user_id]
return cleaned_data
I render this form and the field assignee is a checkbox.
I would like to be able to choose several assignee for the same issue, but I got an error because the Issue model expect just one User instance
How can I modify my model Issue in order to get more than one user ?
Thanks
you can create a new class and name it Issue_Instance where every Issue Object can have an assignee as a foreign key the problem that the relation is one to many because you have to choose more than one assignee and Django doesn't support the idea of having Array or List of Foreign Keys(I don't know any frame works that do :=) ) so I would suggest creating a new class or make the foreign key relation one-to-many key field read about it it will be very useful to solve your problem

How to return data from two different tables in django?

I have two models in my models.py. I need to return a json response which includes data from two tables.
How should my view and serializer look like?
class Device(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
device_name = models.CharField(max_length=200, null=True, blank=True )
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.device_name)
class StatusActivity(models.Model):
OFFLINE = 1
ONLINE = 2
STATUS = (
(OFFLINE, ('Offline')),
(ONLINE, ('Online')),
)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
device_id = models.ForeignKey(Device, related_name='StatusActivity', on_delete=models.CASCADE)
changed_to = models.PositiveSmallIntegerField(choices=STATUS)
modified_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.device_id)
Expected Response:
{
"device_id":"",
"device_name":"",
"changed_to":"",
"modified_at":"",
}
UPDATE:
I set my views.py and serializer.py as below. I am checking
Serializer.py
class DeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = '__all__'
class StatusActivitySerializer(serializers.ModelSerializer):
class Meta:
model = StatusActivity
fields = '__all__'
class ListSerializer(serializers.Serializer):
# devices = DeviceSerializer(many=True)
# activities = StatusActivitySerializer(many=True)
class Meta:
model = [Device, StatusActivity]
fields = ['device_id', 'device_name', 'changed_to', 'modified_at']
Views.py
class DeviceListView(generics.ListAPIView):
queryset = Device.objects.all()
serializer_class = ListSerializer
class StatusActivityListView(generics.ListAPIView):
queryset = StatusActivity.objects.all()
serializer_class = StatusActivitySerializer
Actually you don't need to have two separated views for this, because you can easily serialize relations from one serializer class.
Take a look at this useful answer: How do I include related model fields using Django Rest Framework?
For your case you can write something like this:
class StatusActivitySerializer(serializers.ModelSerializer):
device_name = serializers.CharField(source='device_id.device_name')
class Meta:
model = StatusActivity
fields = ('changed_to', 'modified_at', 'device_id', 'device_name')
Something that worth to note:
it's a good idea for ForeignKey field use device name instead of
device_id;
related_name arg should have a name for reverse access. Keep it
meaningful, e.g. status_activities is a good choice.
filter the status activity. you can refer the foreignkey.
eg:
items = []
for statusact in StatusActivity.objects.all():
items.append({
"device_id":statusact.device_id.id,
"device_name":statusact.device_id.device_name,
"changed_to":statusact.changed_to,
"modified_at":statusact.modified_at,
})

Import column from another table

I made API with Django Restframework.
[models.py]
from django.db import models
class Article(models.Model):
article_no = models.AutoField(primary_key=True)
content = models.CharField(max_length=500, null=False)
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
class Comment(models.Model):
article_no = models.ForeignKey('Article', on_delete=models.CASCADE)
content = models.CharField(max_length=50, null=False, default='')
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
[views.py]
class ArticleDetail(APIView):
def get(self, request, article_no, format=None):
try:
article = models.Article.objects.get(article_no=article_no)
serializer = serializers.ArticleDetailSerializer(article)
return Response(status=status.HTTP_200_OK, data=serializer.data)
except models.Article.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
[urls.py]
urlpatterns = [
path('article/<int:article_no>', views.ArticleDetail.as_view(), name='article_detail'),
]
[serializers.py]
class ArticleDetailSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
comment = CommentSerializer(many=True, read_only=True)
class Meta:
model = models.Article
fields = (
'article_no',
'content',
'password',
'date',
'comment',
)
In serializers.py, I defined comment = CommentSerializer(many=True, read_only=True) and add it to fields.
And to test it, I add comment for article_no=1
But When I connect to /article/1, comment doesn't show anything.
I want to show all comments related it's article_no.
How can I fixed it?
Thanks.
Fixed source is here.
[serializers.py]
class ArticleDetailSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
class Meta:
model = models.Article
fields = (
'article_no',
'content',
'password',
'date',
'comments',
)
[models.py]
class Comment(models.Model):
article_no = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
content = models.CharField(max_length=50, null=False, default='')
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
When I connect to my server,
It only shows related comment's article_no.
But I want to show content and date also.
According to the Django documentation at https://docs.djangoproject.com/en/2.0/topics/db/queries/#backwards-related-objects you can access the list of objects by calling article_instance.comment_set.all() or you could set the related_name argument on the model on initialization
article_no = models.ForeignKey('Article', on_delete=models.CASCADE, related_name="comments")
and access is like article_instance.comments.all() or filter() or exclude()
There are quite a few options actually, and it depends on the use case, but for simplicity, in this case you may be able to just change the comment variable to comment_set, or you could change the related_name to comments and refer to it as such in your serializer.
required changes to ArticleDetailSerializer...
comment = CommentSerializer(many=True, read_only=True)
to
comments = CommentSerializer(many=True, read_only=True)
You also haven't created a CommentSerializer class, or you haven't posted it to the question.
example CommentSerializer....
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model=Comment
exclude=('article_no',)
I exclude the article_no and the remaining fields should be handled due to the assigned defaults on the django models.
The related object manager in Django returns a queryset that can be acted on like any other queryset. So you will want to consider whether an article might have an absurd amount of comments and limit the returned amount.
you can also use a SerializerMethodField and have more control over the returned queryset
comments = serializers.SerializerMethodField()
def get_comments(self, obj):
comments = obj.comments/comment_set.all()[:20] #return the first 20 comments
return CommentSerializer(comments/comment_set, many=True, read_only=True).data
now add comments/comment_set to the class Meta/fields tuple