I have looked at this submission and there is so much clutter in the code I am having a hard time following it: Pass a custom queryset to serializer in Django Rest Framework
Now currently, I am trying to write a serializer that returns a list of all the cities in my table of venues. There may be many venues in each city, but I only want to return the city name once.
I know I need to create a custom model manager for this to modify the queryset, but I am unsure how to pass it to the serializer. I am rather lost in documentation and example. Do I need to write sql do I not? This is why not having a mentor really is a pain.
what I have so far.
models.py:
class Venue(models.Model):
name = models.CharField(max_length=150, blank=False)
description = models.CharField(max_length=1000)
image = models.ImageField(upload_to=imgUnique('venueMedia/venueImages'))
streetAddress= models.CharField(max_length=100)
city = models.CharField(max_length=100, blank=False)
state = models.CharField(max_length=100, blank=False)
serializers.py:
from rest_framework import serializers
from models import Venue, Room
class citySerializer(serializers.Serializer):
and my custom model in models.py:
class CityListManager(models.Manager):
def get_query_set(self):
return super(CityListManager, self).get_query_set().filter
All of the code is incomplete as I figure this out and put it all together
so looks like I was doing it wrong in the first place here is the working code
# queryset for cityListView
def getcityList():
cityList = Venue.objects.raw("""SELECT DISTINCT city
FROM Venue""")
return cityList
class citySerializer(serializers.ModelSerializer):
class Meta:
model = Venue
fields = 'city'
read_only_fields = 'city'
class cityListViewSet(viewsets.viewSet):
def list(self, request):
queryset = getcityList()
serializer = citySerializer(queryset, many=True)
return Response(serializer.data)
Related
In my pervious question I asked how I can automatically save the user submitting the form. I found the form_valid method to be the best in that case. However in my models I also have a user profile model like this
models.py
....
class Profile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
title = models.CharField(max_length=24)
first_name = models.CharField(max_length=35)
last_name = models.CharField(max_length=35)
email = models.EmailField(max_length=64)
phone_number = models.CharField(max_length=12)
department = models.ForeignKey(Department,null=True,on_delete=models.SET_NULL)
supervisor = models.ForeignKey('self',blank=True,null=True,on_delete=models.SET_NULL)
...
As you can see I used the One to One method to make my UserProfile
As before in my models.py I have my reports model
...
class Report(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid1,editable=False)
department = models.ForeignKey(Company,null=True,on_delete=models.SET_NULL)
user= models.ForeignKey(User,on_delete=models.PROTECT)
submission_date= models.DateField(auto_now=True) #invisible to user
submission_time = models.TimeField(auto_now=True) #invisible to ,user
date = models.DateField(default=now,blank=False)
time = models.TimeField(default=now,blank=False,help_text="hh:mm:ss")
location = PlainLocationField()
building = models.ForeignKey(bld,null=True,on_delete=models.SET_NULL)
size = models.PositiveIntegerField()
notes = models.TextField(blank=True)
def __str__(self):
return f'{self.date} {self.time} ({self.department})
...
My question how I can make it so that the department field will load from the user profile? I would hope to eventually make it possible for users in the same department to be able to view and update each others Reports.
As before:
form.py
class ReportForm(forms.ModelForm):
class Meta:
model = Report
fields = '__all__'
location = PlainLocationField()
def redirect():
return redirect("Report")
views.py
class ReportCreate(LoginRequiredMixin,CreateView):
Template = "templates\reports\Report.html"
model = Report
fields = '__all__'
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.save()
return super(ReportCreate, self).form_valid(form)
def get_success_url(self):
return reverse('Report')
def get_absolute_url(self):
return reverse('Report', kwargs={'pk':self.pk})
I advise you to use related_name in your ForeignKeys. Set the department field of both models as following:
class Profile(models.Model):
...
department = models.ForeignKey(Department, null=True, on_delete=models.SET_NULL, related_name='profiles')
...
class Report(models.Model):
...
department = models.ForeignKey(Department, null=True, on_delete=models.SET_NULL, related_name='reports')
...
From now on, Department objects that are related to User.Profile you can access like that:
Department.profiles.all() # it returns QuerySet of all related to Department Profile objects
Department.reports.all() # it returns QuerySet of all related to Department Report objects
And you can use it in making QuerySet for user:
Report.objects.filter(department=self.request.user.profile.department)
# it returns all Report objects, that have exactly the same department as the user
Or using our new relationship:
department = self.request.user.profile.department
reports_for_user = department.reports.all()
But I can see one problem. You are using Company model for ForeignKey in Report. It has to be the same Department model for both Profile and Report models for such easy option to work. Also you definitely should not mix naming in single project. You can set relation with Company as another field:
company = models.ForeignKey(Company, null=True, on_delete=models.SET_NULL)
Here are some steps to help you autofill some fields:
Get the user from self.request.user. How to access current user in Django class based view
Get the profile: get user profile in django
Pass the required fields as context variables: https://www.geeksforgeeks.org/how-to-pass-additional-context-into-a-class-based-view-django/
Pass it into javascript. How can I pass my context variables to a javascript file in Django?
Set the value like this: Set the value of an input field
DONE!
My models have users that can have multiple devices. When I do a GET request on users it returns only the fields specified in the user model, as it should. But I want the option to include in the JSON returned by the GET request the list of devices the user has. How can I do that? Secondly, is there a way I can sometimes get a user with the list of devices in the same JSON, and other times without it? Also, I am really new to Django, and I would appreciate a lot code examples to understand better, if possible.
These are my models:
class User(models.Model):
name = models.CharField(max_length=100)
birth_date = models.DateField()
address = models.CharField(max_length=200)
class Device(models.Model):
description = models.CharField(max_length=200)
location = models.CharField(max_length=200)
max_energy_consumption = models.FloatField()
avg_energy_consuumption = models.FloatField()
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
My serializers:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
class DeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = '__all__'
And the following default ModelViewSets for CRUD api calls:
class UserViewSet(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
class DeviceViewSet(ModelViewSet):
queryset = Device.objects.all()
serializer_class = DeviceSerializer
There are some different ways easiest one would be add a property in your User model and add that to your serializer
class User(models.Model):
name = models.CharField(max_length=100)
birth_date = models.DateField()
address = models.CharField(max_length=200)
#property
def devices(self):
return Device.objects.filter(user_id=self.id).values("location", "description").distinct()
class UserSerializer(serializers.ModelSerializer):
devices = serializers.ReadOnlyField()
class Meta:
model = User
fields = '__all__'
EDIT - for second part of your question:
I have experienced that writing '__all__' in our serializers not the best thing to do when we do not need all the information all the time(performance issues). To address this obsticle making a seperate serializer would be again an easy solution. Whenever I am facing this kind of thing i query same endpoint but send a different qs that i dont use in other endpoint in your case lets say your user viewsets route is something like /api/user/ you can add a qs when you send your get request to your backend and add ?with_devices=true.
Then you can use your second user serializer like this:
class UserViewSet(ModelViewSet):
queryset = User.objects.all()
def get_serializer_class(self):
if self.request.GET.get("with_devices", False):
return UserWithDeviceSerializer
return UserSerializer
where your serializers would be something like:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["name", "birth_date", ..so on]
class UserWithDeviceSerializer(serializers.ModelSerializer):
devices = serializers.ReadOnlyField()
class Meta:
model = User
fields = '__all__'
This would give you what you asked in the comment.
I am trying to update my Teachers view in DRF by instead of including the link to the department field, I would display the name of the department. When I added the PrimaryKeyRelated field, I was able to see the department.name but couldnt use update or create within DRF. Is there a way I could change the display without causing the need for the methods or is that not the case?
Error
The `.update()` method does not support writable dotted-source fields by default.
Write an explicit `.update()` method for serializer `school.serializers.TeacherSerializer`, or set `read_only=True` on dotted-source serializer fields.
The `.create()` method does not support writable dotted-source fields by default.
Write an explicit `.create()` method for serializer `school.serializers.TeacherSerializer`, or set `read_only=True` on dotted-source serializer fields.
models.py
class Department(models.Model):
name = models.CharField(max_length=300)
def __str__(self):
return self.name
class Teacher(models.Model):
name = models.CharField(max_length=300)
department = models.ForeignKey(Department, on_delete=models.CASCADE)
tenure = models.BooleanField()
def __str__(self):
return f'{self.name} teaches {self.department}'
# dont need success url if get_absolute_url on create and update view
def get_absolute_url(self):
return reverse('teacher', kwargs={'pk': self.pk})
serializers.py
class TeacherSerializer(serializers.HyperlinkedModelSerializer):
department = serializers.PrimaryKeyRelatedField(
source='department.name', queryset=Department.objects.all())
class Meta:
model = Teacher
fields = ['url', 'name', 'department', 'tenure']
class DepartmentSerializer(serializers.HyperlinkedModelSerializer):
teacher_set = TeacherSerializer(many=True, required=False)
class Meta:
model = Department
fields = ['url', 'name', 'teacher_set']
views.py
class TeacherViewSet(viewsets.ModelViewSet):
queryset = Teacher.objects.all()
serializer_class = TeacherSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
class DepartmentViewSet(viewsets.ModelViewSet):
queryset = Department.objects.all()
serializer_class = DepartmentSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
have you tried add related_name for model Teacher in field foreign key and call in serializers? link to docs
Hi there Im trying to retrieve a specific object from the related model so as to render data to my view specific to that particular object, in my case I have a custom user model and a related model called Seller.
Models
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
class CustomUser(AbstractUser):
is_customer = models.BooleanField(default=False)
is_seller = models.BooleanField(default=False)
class Seller(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, blank=True, null=True)
store_name = models.CharField(max_length=120)
address = models.CharField(max_length=180)
phone = models.IntegerField(blank=True, null=True)
email = models.CharField( max_length=180, blank=True, null=True )
def __str__(self):
return self.store_name
View
#method_decorator( seller_required , name='dispatch')
class SellerDashBoardView(ListView):
model = Seller
template_name = 'seller_dashboard.html'
def get_context_data(self, *args, **kwargs):
user = CustomUser.seller_set.filter(store_name=self.request.user.username)
context = super(SellerDashBoardView, self).get_context_data( **kwargs)
context['products'] = Product.objects.filter(seller=user)[:6]
return context
This is because when you want to filter ManyToOne reverse Relation, you have to make exact the same query as you would've been done with a direct relation:
CustomUser.objects.filter(seller__store_name="Whole Foods")
# Note that would return a queryset not a single user!
# If you want a CustomUser object you will have to use either get or index the query
The doc example and explanations are provided here:
https://docs.djangoproject.com/en/3.1/topics/db/examples/many_to_one/
It is also better to use prefetch_related method to tell djano ORM that it does not have to make as many queries as number of related objects, that query should be done in 2 database queries instead of lenght of your related query:
CustomUser.objects.prefetch_related("seller_set").filter(seller__store_name="Whole Foods")
The doc link:
https://docs.djangoproject.com/en/3.1/ref/models/querysets/#prefetch-related
You probably would like to use ...seller_set.filter when you already got a CustomUser object. So if you want to filter its sellers you would use that:
...
user.seller_set.filter(store_name="Whole Foods")
That would provide you the Seller objects queryset filtered by a store name related to a specific user. Basically the same query as this:
Seller.objects.filter(user_pk=user.pk, store_name="Whole Foods")
I have two models first as parent model "Country", that filled before the second one as child model "City". as the following
class Country(models.Model):
name = models.CharField(max_length=35)
icon = models.ImageField()
def __str__(self):
return self.name
class City(models.Model):
name = models.CharField(max_length=35)
country = models.ForeignKey(to=Country, on_delete=models.CASCADE)
def __str__(self):
return self.name
My serializers.py for my need as following :
class CountrySerializer(ModelSerializer):
class Meta:
model = Country
fields = '__all__'
class CitySerializer(ModelSerializer):
country = serializers.PrimaryKeyRelatedField(queryset=Country.objects.all())
class Meta:
model = City
fields = ('name', 'country')
view.py
class CountryAPIView(ListAPIView):
queryset = Country.objects.all()
serializer_class = CountrySerializer
permission_classes = [AllowAny, AllowAnonymous]
class CityAPIView(ListAPIView):
queryset = City.objects.all()
serializer_class = CitySerializer
permission_classes = [AllowAny, AllowAnonymous]
def post(self, request):
serializer = CitySerializer(data=request.data)
if serializer.is_valid(raise_exception=ValueError):
serializer.create(validated_data=request.data)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.error_messages,
status=status.HTTP_400_BAD_REQUEST)
now when i run get api it run and gives me a result fine . But when im trying to create a new city and set "country":"id" in json i got this error
Cannot assign "2": "City.country" must be a "Country" instance.
So if i was not clear ,, what i need is exactly set foreign key to city when i create city ,, not create city and country,,
please any one had a solution help, because i tried many ways and read the django rest framework docs about this point but i didn't got it.
First of all, the raise_exception should be a boolean value (either True or False)
You could avoid this error by using inheriting the view class from ListCreateAPIView
from rest_framework.generics import ListCreateAPIView
class CityAPIView(ListCreateAPIView):
queryset = City.objects.all()
serializer_class = CitySerializer
permission_classes = [AllowAny, AllowAnonymous]
You don't want to use the post() method if you're using ListCreateAPIView, because DRF will take care of that part well.
Suggestion
Since you're dealing with CRUD functionalities of the model, you can use the DRF's ModelViewset class
you are not using the validated data to create a new city, just change this line:
serializer.create(validated_data=request.data)
to this:
serializer.save()
when you perform serializer.save(), the serializer will use its validated data.
also, DRF has a generic view(ListCreateAPIView) that covers your use-case.