How to make Graphene Input Class variables optional? - graphene-python

In a GraphQL update mutation I want to be able to pass in the values for a child object but I want each of those values to be optional.
So I have created an Input Class like this:
class CityCouncilInput(graphene.InputObjectType):
mayor = graphene.String()
treasurer = graphene.String()
Now, I want to be able to pass in either values for both the mayor and treasurer or just one of them.
Pleas know that my code works fine if ALL of the values are passed in. I just want those field values to be optional. How do I do that?
Robert

You can try
class CityCouncilInput(graphene.InputObjectType):
mayor = graphene.String(required=False, default=None)
treasurer = graphene.String(required=False, default=None)

I think the easiest is to define a default argument for the mutation function
Assuming you have the following model, where your values can be blank (Note: I assumed both mayor and treasurer would be allowed to be blank but not NULL - otherwise I guess you can pass None as a default argument):
class CityCouncil(models.Model):
mayor = models.TextField(max_length=1000, blank=True)
treasurer = models.CharField(max_length=1000, blank=True)
Then to create the city council this should work:
class createCityCouncil(graphene.Mutation):
mayor = graphene.String()
treasurer = graphene.String()
class Arguments:
mayor = graphene.String()
treasurer = graphene.String()
def mutate(self, mayor="", treasurer=""):
council = CityCouncil(mayor=mayor, treasurer=treasurer)
council.save()
return createCityCouncil(
mayor=council.mayor,
treasurer=council.treasurer
)
Similarly, when performing an update mutation, you can pass in optional arguments and selectively update the property on your object with setattr).
class updateCityCouncil(graphene.Mutation):
mayor = graphene.String()
treasurer = graphene.String()
class Arguments:
mayor = graphene.String()
treasurer = graphene.String()
def mutate(self, info, id, **kwargs):
this_council=CityCouncil.objects.get(id=id)
if not this_council:
raise Exception('CityCouncil does not exist')
for prop in kwargs:
setattr(this_council, prop, kwargs[prop])
this_council.save
return updateCityCouncil(
mayor=this_council.mayor,
treasurer=this_council.treasurer
)

You may just have to run an if statement in your mutate function to have the capability of updating either field and the code for the class updateCityCouncil mutation is as below (PS: Take note of assumptions of a model named CityCouncil and the GraphQL type for the Model):
class CityCouncilType(DjangoObjectType):
class Meta:
model = CityCouncil
class CityCouncilInput(graphene.InputObjectType):
id = graphene.Int()
mayor = graphene.String()
treasurer = graphene.String()
class updateCityCouncil(graphene.Mutation):
class Arguments:
id = graphene.Int(required=True)
input = CityCouncilInput(required=True)
ok = graphene.Boolean()
council = graphene.Field(CityCouncilType)
#staticmethod
def mutate(root, info, id, input=None):
ok = False
this_council = CityCouncil.objects.get(pk=id)
if this_council:
ok = True
if input.mayor is not None:
this_council.mayor = input.mayor
if input.treasurer is not None:
this_council.treasurer = input.treasurer
this_council.save()
return updateCityCouncil(ok=ok, council=this_council)
return updateCityCouncil(ok=ok, council=None)

Related

TypeError: Field 'id' expected a number but got <class 'rest_framework.fields.CurrentUserDefault'>

Seems I need to apply a dot notation to CurrentUserDefault() object, tried .id but failed
class DotPrivateSerializer(serializers.ModelSerializer):
tag = serializers.SerializerMethodField()
def get_tag(self,obj):
queryset=TagPrivate.objects.filter(user=serializers.CurrentUserDefault) # <--TypeError
return TagPrivateSerializer(queryset).data
models.py
class DotPrivate(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
name = models.CharField(max_length=255)
description = models.TextField(max_length=350, blank=True)
lon = models.CharField(max_length=20)
lat = models.CharField(max_length=20)
rating = models.FloatField(validators=[MinValueValidator(0.0), MaxValueValidator(5.0)])
tag = models.ForeignKey('TagPrivate', on_delete=models.PROTECT)
in the following link in the first answer I found some solution but I do not completly understand it:
'CurrentUserDefault' object has no attribute 'user'
class TagPrivateSerializer(serializers.ModelSerializer):
class Meta:
model = TagPrivate
fields = ('id', 'name')
read_only_fields = ('id',)
You can not use CurrentUserDefault, this is just a value that the Django serializer will use for a default=… parameter, and then later swap for the request user.
You can fetch this from the request in the context, so:
class DotPrivateSerializer(serializers.ModelSerializer):
tag = serializers.SerializerMethodField()
def get_tag(self, obj):
queryset = TagPrivate.objects.filter(user=self.context['request'].user)
return TagPrivateSerializer(queryset).data
In the ModelViewSet, you will need to pass the user, so:
class DotPrivateViewSet(ModelViewSet):
queryset = # …
permission_classes = # …
serializer_class = DotPrivateSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context.update(request=self.request)
return context

Update method of serializer only creates and doesn't update (Django rest framework)

I'm having a hard time with nested serializers, especially updating them. So far I can update the lesson description and all the main fields before the nested serializer (like course description, title etc). Ideally I'd like to search using lesson_id, and not lesson.title like it is now, and update description and title at the same time. Is there a workaround for that?
My models.py
class Course (models.Model):
title = models.CharField (max_length=150)
description = models.CharField(max_length=250, default="No Description")
student = models.ManyToManyField(Student, related_name='courses', blank=True)
teacher = models.ManyToManyField(Teacher, related_name='teacher', blank=True)
def __str__(self):
return self.title
class Lesson (models.Model):
title = models.CharField(max_length=100)
description = models.TextField(default="No Description")
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='lessons')
def __str__(self):
return self.title
Serializers.py where the problem is
class CourseSerializer (serializers.ModelSerializer):
lessons = LessonSerializer(many=True, required=False)
#teacher = TeacherSerializer(many=True)
class Meta:
model = Course
fields = ('id', 'title', 'description', 'lessons') #to separate serializer with students for teachers later
def update(self, instance, validated_data):
lessons = validated_data.pop('lessons', [])
instance = super().update(instance, validated_data)
for lesson in lessons:
lesson, updated = Lesson.objects.update_or_create( defaults={'description': lesson["description"]}, title= lesson["title"])
#pk = instance.lessons_id doesn't work, I cannot get this id
instance.save()
return instance
Views
class CourseDetailDeleteView (generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsAdminUserOrAuthenticatedOrReadOnly,)
queryset = Course.objects.all()
serializer_class = CourseSerializer
def update(self, request, *args, **kwargs):
serializer = CourseSerializer(instance=self.get_object(), data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
If your problem is that validated_data["lessons"][0]["id"] doesn't exists:
By default the id field of a ModelSerializer is read_only, so id is not included in validated_data in .create() or .update(). Thus you'll have to override that:
class LessonSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
....
## Optional: in case you don't want `id` getting explicitly set
def create(self, validated_data):
validated_data.pop("id", None)
return super().create(self, validated_data)
## Optional: in case you don't want `id` of the instance getting updated
def update(self, instance, validated_data):
validated_data.pop("id", None)
return super().update(self, instance, validated_data)
Another note:
There's no way to know if serializer.save() is even called in CourseDetailDeleteView.update. I would suggest to set serializer.is_valid(raise_exception=True) instead so it can return error messages and show you why it didn't save.

How to get data from multiple models in djago rest framework

I am new in Django Rest APi so Please help.
I have Two models :
class Department(models.Model):
name = models.CharField(max_length = 50)
location = models.CharField(max_length=20)
dept_code = models.CharField(max_length=5)
class Employee(models.Model):
name = models.CharField(max_length = 50)
email = models.EmailField()
mobile = models.CharField(max_length=10)
department = models.ForeignKey(Department, on_delete= models.CASCADE)'
And Respective serializers are:
class EmployeeSerializer(serializers.ModelSerializer):
class Meta:
model = Employee
fields = '__all__'
class DepartmentSerializer(serializers.ModelSerializer):
class Meta:
model = Department
fields = '__all__'
I want to retrieve the data in like this
{
emptId : 1,
empName : 'John',
empEmail : 'John#gmail.com',
empMobile : '1111111111',
deptId : 1,
deptName : 'Accounts',
deptLocation : 'Pune',
deptCode : 'ACC'
}
How to get the above data. Please help me
In Your Serializers.py
class DepartmentSerializer(serializers.ModelSerializer):
class Meta:
model = Department
fields = '__all__'
class EmployeeSerializer(serializers.ModelSerializer):
department = DepartmentSerializer(many=False)
class Meta:
model = Employee
fields = ('empId','empName','empEmail','empMobile','department')
using this you will get nested object structure which is preferred.
If you want to get flatten structure then
class DepartmentSerializer(serializers.ModelSerializer):
class Meta:
model = Department
fields = '__all__'
class EmployeeSerializer(serializers.ModelSerializer):
department = DepartmentSerializer(many=False)
depId = serializers.IntergerField(source='department.id')
depName = serializers.CharField(source='department.name')
depLocation = serializers.CharField(source='department.location')
depCode = serializers.CharField(source='department.dept_code')
class Meta:
model = Employee
fields = ('empId','empName','empEmail','empMobile','depId','depName','depLocation','depCode','department')
Hope you get this
If you don't want department object in get method then add write_only=True in argument of department field.
see more
readonly and writeonly
medium
You can write your serializer class like this:
class MySerializer(serializers.Serializer):
emptId = serializers.IntergerField()
empName = serializers.CharField()
empEmail = serializes.CharField()
empMobile = serializes.CharField()
depId = serializers.IntergerField()
depName = serializers.CharField()
depLocation = serializers.CharField()
depCode = serializers.CharField()
class Meta:
fields = ('emptId', 'empName', 'empEmail', 'empMobile', 'depId', 'depName', 'depLocation', 'depCode',)
And you can write your view class like this:
class MyAPIView(APIView):
def get(self, request, *args, **kwargs):
employeeId = 1
myEmployee = Employee.objects.filter(id=employeeId) \
.annotate(emptId=F('id'),
empName=F('name'),
empEmail=F('email'),
empMobile=F('mobile'),
depId=F('department__id'),
depName=F('department__name'),
depLocation=F('department__location')
depCode=F('department__dep_code').values('emptId', 'empName', 'empEmail', 'empMobile', 'depId', 'depName', 'depLocation', 'depCode').first()
mySerializer = MySerializer(myEmployee)
return Response(mySerializer, status=status.HTTP_200_OK)
I do not test the code in a real program. But you can write your code like my code. You can inspire of that.

Is there more better cancel nested ManyToMany field unique check

# models.py
class Student(models.Model):
name = models.CharField( max_length=256)
student_numer = models.CharField(max_length=256)
teachers = models.ManyToManyField(Teacher)
class Teacher(models.Model):
name = models.CharField(max_length=256)
# serializers.py
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = ('name', 'student_number', )
class TeacherSerializer(serializers.ModelSerializer):
students = StudentSerializer(many=True)
class Meta:
model = Teacher
fields = '__all__'
def update(self, instance, validated_data):
students = validated_data.pop('students')
# here I want delete all old students, and add all new students
# of course, I have more better way to update it, but here just for simple.
But in fact, if I update teacher instance, and if the students not change, it will
raise: student with this student_numer already exist.
I know why, because StudentSerializer's student_numer field has the validator check unique.
And I can add some code like this to fixed this problem:
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = ('name', 'student_number', )
extra_kwargs = {
'student_numer': {
'validators':[]
}
}
Now I want to know is there any more better way ??

Passing argument from view to Custom RelatedField serializer

How can I pass an argument to a serializers.RelatedField class from views.py. I need to pass language_id to query Language.objects model within that RelatedField.
I am not sure if I took a right approach to this issue. What I want to achieve is to present information about genres associated to a movie from database model about depending on the language. The MovieGenre model has genre ID field which I want to replace with actual Genre name.
My serialiser.py
class GenreField(serializers.RelatedField):
def to_representation(self, value, language_id=1):
genre_name = GenresVideo.objects.get(genre_id=value, language_id=language_id)
return genre_name.name
class MovieGenresSerializer(serializers.ModelSerializer):
genre_id = GenreField(read_only=True)
class Meta:
model = MoviesGenres
As you see, here I query Language.objects with default value but I would like to pass it from views (language_id).
My views.py:
class MovieGenresTestViewSet(viewsets.ModelViewSet):
lookup_field = 'movie'
queryset = MoviesGenres.objects.all()
serializer_class = MovieGenresSerializer
def list(self, request, language_pk):
queryset = MoviesGenres.objects.all()
serializer = MovieGenresSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, movie, language_pk):
queryset = MoviesGenres.objects.filter(movie=movie)
serializer = MovieGenresSerializer(queryset, many=True)
return Response(serializer.data)
And my urls.py:
router.register(r'lang', LanguagesViewSet, base_name='lang')
mov_gen = routers.NestedSimpleRouter(router, r'lang', lookup='language')
mov_gen.register(r'mg', MovieGenresTestViewSet, base_name='mg')
url(r'^api/', include(genre_spec.urls))
My models.py
class Languages(models.Model):
name = models.CharField(unique=True, max_length=255)
short_name = models.CharField(unique=True, max_length=4, blank=True, null=True)
active = models.BooleanField(default="")
class Meta:
managed = False
db_table = 'languages'
ordering = ('id',)
class GenresVideo(models.Model):
genre_id = models.IntegerField()
language = models.ForeignKey('Languages')
name = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = False
db_table = 'genres_video'
unique_together = (('genre_id', 'language'),)
ordering = ('genre_id',)
class MoviesGenres(models.Model):
movie = models.ForeignKey(Movies)
genre_id = models.IntegerField()
class Meta:
managed = False
db_table = 'movies_genres'
unique_together = (('movie', 'genre_id'),)
Through the urls routes, I can get a correct response from API including the language_id. I just need to pass it to the view somehow.
Thanks a lot for help!
I'll try to answer to your first question, with the easiest implementation possible: SerializerMethodField. Because we will get the language id via the context passed to the serializer, we should either generate the context for the serializer, or let the framework do that for us.
Now to the problem at hand: you aren't filtering the queryset (MoviesGenres) by language per se. Thus, we can avoid overwriting the list and retrieve methods. Nevertheless, the router mechanism will inject in kwargs for the view method the language_pk parameter - that's the parameter that we will retrieve from within the serializer context:
class MovieGenresSerializer(serializers.ModelSerializer):
genre = searializers.SerializerMethodField()
class Meta:
model = MoviesGenres
def get_genre(self, instance):
# get the language id from the view kwargs
language_id = self.context['view'].kwargs['language_pk']
# get the genre
try:
genre_name = GenresVideo.objects.get(genre_id=instance.genre_id, language_id=language_id).name
except GenresVideo.DoesNotExist:
genre_name = None
# return the formatted output
return genre_name
class MovieGenresTestViewSet(viewsets.ModelViewSet):
lookup_field = 'movie'
queryset = MoviesGenres.objects.all()
serializer_class = MovieGenresSerializer

Resources