I've got a database with an simple Employee model and node in Django. I´m using Graphene to create an API around this that allows a user to retrieve the right data.
class Employee(models.Model):
id = models.UUIDField(primary_key=True, unique=True, )
name = models.CharField(max_length=128)
class EmployeeNode(DjangoObjectType):
class Meta:
model = Employee
fields = "__all__"
interfaces = (graphene.relay.Node, )
Now in addition to this, I have a query that finds a "buddy" for every Employee, which is another Employee (ID) in the database, and a function (details irrelevant here) that finds the correct "buddy" in the database using some not further specified Django query.
class EmployeeNodeWithBuddy(DjangoObjectType):
buddy_id = graphene.UUID()
class Meta:
model = Employee
fields = "__all__"
interfaces = (graphene.relay.Node, )
#classmethod
def get_queryset(cls, queryset, info):
set_with_buddy_annotation = queryset.annotate(
buddy_id=ExpressionWrapper(Subquery(
### Omitting the details of this query ###
).only('id')[:1], output_field=models.UUIDField()
), output_field=models.UUIDField())
)
return set_with_buddy_annotation
This works ok, but what I actually want is not the ID of the buddy, but the actual EmployeeNode. I can´t figure out if there is a good way to annotate/add info to this query to make it return the thing I want. It would look like:
class EmployeeNodeWithBuddy(DjangoObjectType):
buddy_id = graphene.UUID()
buddy = graphene.Field(EmployeeNode) # Field with EmployeeNode instead of just ID!
class Meta:
model = Employee
fields = "__all__"
interfaces = (graphene.relay.Node, )
#classmethod
def get_queryset(cls, queryset, info):
set_with_buddy_annotation = queryset.annotate(
buddy_id=ExpressionWrapper(Subquery(
### Omitting the details of this query ###
).only('id')[:1], output_field=models.UUIDField()
), output_field=models.UUIDField())
)
set_with_buddy_annotation = # Somehow fetch the EmployeeNode from the buddy_id here?
return set_with_buddy_annotation
Does this make sense to do and is this even possible?
Related
These are simplified versions of my models (the user model is just an id and name)
class Convo(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='convo_owner')
users = models.ManyToManyField(User, through='Convo_user')
class Convo_user (models.Model):
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
convo = models.ForeignKey(Convo, on_delete=models.CASCADE)
class Comments(models.Model):
name = models.CharField(max_length=255)
content = models.TextField(max_length=1024)
convo = models.ForeignKey(Convo, on_delete=models.CASCADE)
This is my view
class ConvoViewSet(viewsets.ModelViewSet):
serializer_class = serializers.ConvoSerializer
def get_queryset(self):
return None
def list(self, request):
curr_user = request.user.id
# Collecting the list of conversations
conversations = models.Conversation.object.filter(ConvoUser__user_id=request.user.id)
#Getting list of conversation id's
conv_ids = list(conversations.values_list('id', flat=True).order_by('id'))
#Getting list of relevant comments
comments = models.Comments.objects.filter(conversation_id__in=conv_ids)
return Response(self.get_serializer(conversations, many=True).data)
And my current serializer
class ConvoSerializer(serializers.ModelSerializer):
"""A serializer for messaging objects"""
# access = AccessSerializer(many=True)
# model = models.Comments
# fields = ('id', 'name', 'content', 'convo_id')
class Meta:
model = models.Convo
fields = ('id', 'owner_id')
The current response I get is of the form
[
{
"id": 1,
"owner_id": 32
}, ...
]
But I would like to add a comments field that shows all the properties of comments into the response, so basically everything in the second queryset (called comments) and I'm not sure how to go about this at all. (I retrieve the comments in the way I do because I'm trying to minimize the calls to the database). Would I need to create a new view for comments, make its own serializer and then somehow combine them into the serializer for the convo?
The way you've set up your models, you can access the comments of each Convo through Django's ORM by using convo_object.comments_set.all(), so you could set up your ConvoSerializer to access that instance's comments, like this:
class ConvoSerializer(serializers.ModelSerializer):
"""A serializer for messaging objects"""
comments_set = CommentSerializer(many=True)
class Meta:
model = models.Convo
fields = ('id', 'owner_id', 'comments_set')
and then you define your CommentSerializer like:
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = models.Comments
fields = ('id', 'name', 'content')
No data appears because my serializers are using the default database, not sure why but a step forward
EDIT:
Django: Database used for prefetch_related is not the same that the parent query Provided me the correct answer, I was able to choose the database with this method because for some reason inner queries use the default DB
I've the following models:
class ModelX(models.Model):
STATUS_CHOICES = (
(0, 'ABC'),
(1, 'DEF'),
)
status = models.IntegerField(choices=STATUS_CHOICES)
user = models.ForeignKey(Users)
class Users(models.Model):
phone_number = models.Charfield()
and the serializer for ModelX is :
class ModelXSerializer(serializers.ModelSerializer):
phone_number = serializers.PrimaryKeyRelatedField(
source='user', queryset=Users.objects.get(phone_number=phone_number))
class Meta:
model = ModelX
fields = ('phone_number',)
In the request for creating the ModelX record, I get phone_number instead of the user_id. Now, I've to fire a filter query in order get the user instance. How do I do that, ie Users.objects.get(phone_number=phone_number)
Also, when creating a record, the status field will always be 0. The client wont post the status parameter in the body. Its the internal logic. Is there a way in serializers that can set the status field to 0 automatically. Please dont recommend to set this field as default=0 in models. There's some logic behind this. This is just a shorter version of the problem statement.
you can try such version of your serializer, with custom create method:
class ModelXSerializer(serializers.ModelSerializer):
phone_number = serializers.CharField(source='user.phone_number')
class Meta:
model = ModelX
fields = ('phone_number',)
def create(self, validated_data):
phone_number = validated_data['phone_number']
user = Users.objects.get(phone_number=phone_number)
instance = ModelX.objects.create(status=0, user=user)
return instance
details about the source argument.
You can add it in validate function:
class ModelXSerializer(serializers.ModelSerializer):
def validate(self, attrs):
attrs = super(ModelXSerializer, self).validate(attrs)
attrs['status'] = 0
return attrs
i have two models
class Sku(models.Model):
manufacturer = models.ForeignKey('Manufacturer')
class Manufacturer(models.Model):
title = models.CharField()
I want that in the filtering appeared only manufacturers associated with the current set of sku.
my view part:
c['skus'] = self.object.skus.filter(hide=False, prices__price_type=PRICE_ROZN).prefetch_related('prices',
'stock').all().order_by(
'prices__value')
sku_filter = SkuFilter(self.request.GET, c['skus'])
If Self existed at this moment, I would filter out the manufacturers in this way:
class SkuFilter(django_filters.FilterSet):
# manufacturer__title = django_filters.CharFilter(lookup_expr='icontains')
manufacturer = django_filters.filters.ModelMultipleChoiceFilter(
name='manufacturer',
to_field_name='title',
queryset=Manufacturer.objects.filter(
pk__in=self.queryset.objects.values_list('manufacturer').distinct()),
)
class Meta:
model = Sku
fields = ['manufacturer', ]
But it is obvious that at the given moment the self does not yet exist.
I solved this question by this method:
create field without filttration like (Manufacturer.objects.all())
wait for metaclass magic create base_fitler field
override init and replace current filter
class SkuFilter(django_filters.FilterSet):
def __init__(self, data=None, queryset=None, prefix=None, strict=None):
self.base_filters['manufacturer'] = django_filters.filters.ModelMultipleChoiceFilter(
name='manufacturer',
to_field_name='title',
queryset=Manufacturer.objects.filter(
pk__in=queryset.values_list('manufacturer').distinct()),
)
super().__init__(data, queryset, prefix, strict)
manufacturer = django_filters.filters.ModelMultipleChoiceFilter(
name='manufacturer',
to_field_name='title',
queryset=Manufacturer.objects.all()
)
class Meta:
model = Sku
fields = ['manufacturer', ]
I'm trying to create a custom serializer method that counts the number of passed and failed quizzes from my QuizResults model. A failed quiz is under .7 and a passed quiz is .7 or over.
I want to be able to look into the Users QuizResult and count the number of passed quizzes(.7 or over). I would then duplicate the method to count the failed quizzes (under .7).
So far I don't have much idea on how to do so. I want to be able to grab the percent_correct field of the model and do a calculation and add it to a field in the serializer called "quiz_passed".
Here is my QuizResult model:
class QuizResult(models.Model):
quiz = models.ForeignKey(Quiz)
user = models.ForeignKey(User, related_name='quiz_parent')
percent_correct = models.FloatField(validators=[MinValueValidator(0.0), MaxValueValidator(1.0)])
date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return 'Quiz Results for : ' + self.quiz.title
Here is my serializer:
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
todo_count = serializers.IntegerField(source='todo_parent.count', read_only=True)
discussion_count = serializers.IntegerField(source='comment_parent.count', read_only=True)
quiz_passed = serializers.SerializerMethodField()
class Meta:
model = User
fields = ('todo_count', 'discussion_count', 'quiz_passed', 'username', )
def get_quiz_passed(self, obj):
return passed
Any help is appreciated.
Edit:
I extended the User model and added a model method like you suggested.
class Profile(User):
def get_quizzes_passed_count(self):
return self.quiz_parent.filter(percent_correct__gte=0.8).count()
I then added your suggestion to my ProfileSerializer.
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
todo_count = serializers.IntegerField(source='todo_parent.count', read_only=True)
discussion_count = serializers.IntegerField(source='comment_parent.count', read_only=True)
num_quizzes_passed = serializers.ReadOnlyField(source="get_quizzes_passed_count")
class Meta:
model = Profile
fields = ('todo_count', 'discussion_count', 'num_quizzes_passed', 'username')
Unfortunately when I add this nothing appears in the framework once these have been added. Any suggestions? Thank you.
You can use a model method on the user model to count that user's number of passed quizzes:
class User(models.model):
# rest of your User attributes
def get_quizzes_passed_count(self):
return self.quiz_parent.filter(percent_correct__gte=0.7).count()
Then add that to your serializer using a DRF ReadOnlyField to serialize that method:
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
todo_count = serializers.IntegerField(
source='todo_parent.count', read_only=True
)
discussion_count = serializers.IntegerField(
source='comment_parent.count', read_only=True
)
quiz_passed = serializers.SerializerMethodField()
num_quizzes_passed = serializers.ReadOnlyField(source="get_quizzes_passed_count")
class Meta:
model = User
fields = ('todo_count', 'discussion_count', 'quiz_passed', 'username', )
def get_quiz_passed(self, obj):
return passed
You can duplicate this for the number of failed quizzes.
what I'm trying to do, is to convert unix_epoch into python dateTime and vice versa while serialization and desiarilization. What I've done so far:
My model Track:
class Track(models.Model):
uuid = models.UUIDField(null=True)
distance = models.FloatField(null=True)
dateCreated = models.DateTimeField(null=True)
dateClosed = models.DateTimeField(null=True)
def date_created_to_epoch(self):
return time.mktime(self.dateCreated.timetuple())
def date_closed_to_epoch(self):
return time.mktime(self.dateClosed.timetuple())
My model Point:
class Point(models.Model):
uuid = models.UUIDField(null=True)
track_uuid = models.UUIDField(null=True)
dateCreated = models.DateTimeField(null=True);
track = models.ForeignKey(Track, related_name='points')
def date_created_to_epoch(self):
return time.mktime(self.dateCreated.timetuple())
My serializers:
class PointSerializer(serializers.ModelSerializer):
dateCreated = serializers.ReadOnlyField(source='date_created_to_epoch')
class Meta:
model = Point
fields = ('uuid', 'lat', 'lng')
class TrackSerializer(serializers.ModelSerializer):
points = PointSerializer(many=True)
dateCreated = serializers.ReadOnlyField(source='date_created_to_epoch')
dateClosed = serializers.ReadOnlyField(source='date_closed_to_epoch')
class Meta:
model = Track
fields = ('uuid', 'distance', 'dateCreated', 'dateClosed', 'comments', 'type', 'status', 'points')
def create(self, validated_data):
points_data = validated_data.pop('points')
track = Track.objects.create(**validated_data)
for point_data in points_data:
Point.objects.create(track=track, **point_data)
return track
As you see, I've made from database to json convertion with def date_created_to_epoch(self): method. What I need is to implement json(unix time) to database (date time) convertion. I use nested models. I think my methods are incorrect and there is a way to make it better. Please help.
I would use Django Rest Framework's built in DateTime fields instead of the raw .ReadOnlyField. You can make any field readonly by passing read_only=True to the field's constructor.