Pulling and showing data from associate table in Django rest framework - django

I am trying to build an API using Django Rest Framework. Which i am not familiar with. I want to know how I can pull data using references and associated tables. I have three models Users, Company and Reviews. I am storing and Auth_token in user table and I want to be able to pull reviews by a certain user by putting the auth token in the address bar.
my models are
class User(models.Model):
user_name = models.CharField(max_length=45)
email = models.CharField(max_length=255)
auth_Token = models.CharField(max_length=100,default=uuid.uuid4)
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
def __str__(self):
return self.user_name
class Company(models.Model):
company_name = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
def __str__(self):
return self.company_name
class Review(models.Model):
title = models.CharField(max_length=64)
rating = models.IntegerField(blank=False)
summary = models.TextField(max_length=10000)
ip = models.GenericIPAddressField()
company = models.ForeignKey(Company)
user = models.ForeignKey(User)
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
def __str__(self):
return self.title
I am currently able to pull reviews using following apiview:
class ReviewView(APIView):
def get(self,request):
reviews = Review.objects.all()
serializer = ReviewSerializer(reviews, many=True)
return Response(serializer.data)
and the following serilaizer:
class ReviewSerializer(serializers.ModelSerializer):
class Meta:
model = Review
fields = ('title','company', 'rating', 'summary','user')
Please ignore the indentations. However this results in me getting back the company id and user id only. I basicaly want to know two things here.
First how do i pull data where auth token is passed as the url
url(r'^reviews/(?P<auth_Token>[\w-]+)/$', ReviewView.as_view()),
and second how do i display company name and user name instead of ids. Any help would be great. Thanks

How do I filter reviews based on the user's auth token?
I will suggest using a ReadOnlyModelViewSet. It will greatly reduce your view code and allow for easy filtering. Most of the mundane and tiring code of handling requests, validating and so on has already been written in these viewsets and therefore, you can just focus on your business logic rather than server side details.
Instead of using an auth_Token in the URL param itself (eg. reviews/XXX/), I have placed it as a query param (eg. reviews/?auth_Token=XXX). The reason behind this is because the URL param itself should return a specific review resource but you need to return a list of filtered reviews mapped to one user.
from rest_framework import viewsets
class ReviewView(viewsets.ReadOnlyModelViewSet):
serializer_class = ReviewSerializer
def get_queryset(self):
"""
This function is called whenever someone tries to retrieve reviews.
You do not need to worry about serialization or handling the response
as the viewset has set that up with your specified serializer_class
"""
auth_Token = self.query_params.get("auth_Token", None)
if auth_Token: # They provided an auth token so we need to filter.
reviews = Review.objects.filter(user__auth_Token=auth_Token)
else:
reviews = Review.objects.all()
return reviews
In your urls:
url(r'^reviews/$', ReviewView.as_view({"get":"list"})),
How do I show the company name and user name in retrieved reviews and not show their ids?
You need to use a SlugRelatedField (http://www.django-rest-framework.org/api-guide/relations/#slugrelatedfield). This field allows you to replace the typical id with another attribute found in the associated table. Please see the new ReviewSerializer below:
class ReviewSerializer(serializers.ModelSerializer):
company = serializers.SlugRelatedField(read_only=True, slug_field="company_name")
user = serializers.SlugRelatedField(read_only=True, slug_field="user_name")
class Meta:
model = Review
fields = ('title','company', 'rating', 'summary','user')

Related

Django annotate with other model attributes based on a common field

I have a User model,
class User(AbstractBaseUser):
id = models.IntegerField(primary_key=True)
email = models.EmailField(unique=True)
I have another model named Company. The Company model has a reference to User model via an Integer field.
class Company(models.Model):
user_id = models.IntegerField(db_index=True)
name = models.CharField(max_length=100)
size = models.IntegerField(default=1)
I wanted to extract the company information along with user information.
basically I want a user object dictionary like this {'id':1, 'email':'abc#gmail.com','name':'foobar.co','size':400}
I want to annotate the user objects with name and size. As of now, I tried in the serializer's to_representation method. However, for millions of users this is super slow.
class UserSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
email = serializers.EmailField(read_only=True)
def to_representation(self, instance):
response = super(UserSerializer, self).to_representation(instance)
company = Company.objects.filter(user_id=instance.id)
if company.exists():
company = company.first()
response['name'] = company.name
response['size'] = company.size
return response
How can I achieve this annotation in the query itself.
If the links in the comment do not help you, You can use SerializerMethodField for name, and size
class UserSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
email = serializers.EmailField(read_only=True)
name = serializers.SerializerMethodField()
size = serializers.SerializerMethodField()
def get_name(self, obj):
# get name from DB using the User object(obj)
return name
def get_size(self, obj):
# get size from DB using the User object(obj)
return size

Django Model advance Foreignkey Relation

I have a Django model which has relationship with user model. Where user are assigned to groups. especially "Admin", "Teacher","Student". So I want to make a foreign key relationship in such a way that it will show only The users that have been assigned to Teacher groups for Teacher_details model, And Similar for Student_Details Model. I have made the models Teacher_Details , Student_Details and established foreign key relation with User model. But the problem is that its showing all the user when I am filling Student_Details or Teacher_Details. Hope you got my problem.
I am hoping positive response.
The code looks like this:
class Student_Details(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
image = models.ImageField(default='none', upload_to='img/')
details_updated = models.DateTimeField(auto_now_add=True)
address = models.CharField(max_length=150)
admission_date = models.DateField()
previous_college = models.CharField(max_length=150)
course_enrolled = models.ForeignKey(ModelUniversity,on_delete=models.CASCADE)
semester = models.CharField(max_length=20,choices=semester,default=None)
def __str__(self):
return self.user.first_name
class Teacher_Details(models.Model):
address = models.CharField(max_length=150)
image = models.ImageField(default='none', upload_to='img/')
details_updated = models.DateTimeField(auto_now_add=True)
subject_taught = models.ManyToManyField(to='Student.stu_subject')
user = models.OneToOneField(User,on_delete=models.CASCADE)
def __str__(self):
return self.user.first_name
def subject_teacher_teaches(self):
return [str(s) for s in self.subject_taught.all()]
Since both models have a user_id reference, you could use that info to search both models based on the request and fetch the necessary instance. Make a view which checks the user_id in the request, search both models and return the results (I assume a user cannot belong to both groups...)

Use parameters in subfields with graphene-django without relay for pagination purpose

I'm using graphene with django and I'm struggling to do something that in my head should be very simple, but I don't find it documented anywhere in graphene docs or github nor did I see similar question here. The closest to it I found was:
https://www.howtographql.com/graphql-python/8-pagination/ but as you can see I'd have to declare the parameters in the parent resolver which I don't want to.
I have a query like this
getUser(id: $userIdTarget) {
id
username
trainings{
id
name
sessions{
id
name
}
}
}
}
I would like to implement a pagination in the sessions subfield. So this is what I would like:
getUser(id: $userIdTarget) {
id
username
trainings{
id
name
sessions(first:10){
id
name
}
}
}
}
and in the resolver I'd implement something like this:
def resolve_sessions(root, info, first=None, skip=None):
if skip:
return gql_optimizer.query(Session.objects.all().order_by('-id')[skip:], info)
elif first:
return gql_optimizer.query(Session.objects.all().order_by('-id')[:first], info)
else:
return gql_optimizer.query(Session.objects.all().order_by('-id'), info)
(gql_optimizer is just an optimization wrapper library I use)
However this doesn't work as the field sessions correspond to a list of a model Session that is a fk to Training according to my django models, so this is automatically resolved by graphene because these types are DjangoObjectType , so I'm not really sure how can one customize these resolvers (or if it's even possible).
I'll leave the relevant models and types below:
Session model
class Session(models.Model):
name = models.CharField(max_length=200, help_text='Session\'s name')
category = models.CharField(max_length=240, choices=SESSION_CATEGORIES, default="practice",
help_text='Session type. Can be of \'assessment\''
'or \'practice\'')
total_steps = models.IntegerField(default=1, help_text='Amount of steps for this session')
created_at = models.DateTimeField(editable=False, default=timezone.now, help_text='Time the session was created'
'(Optional - default=now)')
completed_at = models.DateTimeField(editable=False, null=True, blank=True, help_text='Time the session was finished'
'(Optional - default=null)')
is_complete = models.BooleanField(default=0)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="training_sessions", on_delete=models.DO_NOTHING)
training = models.ForeignKey("Training", related_name="sessions", on_delete=models.CASCADE)
def __str__(self):
return self.name
UserType
class UserType(DjangoObjectType):
class Meta:
model = get_user_model()
fields = "__all__"
#classmethod
def get_queryset(cls, queryset, info, **kwargs):
if info.variable_values.get('orgId') and info.variable_values.get('orgId') is not None:
return queryset.order_by('username')
return queryset
SessionType
class SessionType(DjangoObjectType):
class Meta:
model = Session
fields = "__all__"
convert_choices_to_enum = False
#classmethod
def get_queryset(cls, queryset, info, **kwargs):
if info.variable_values.get('userId') and info.variable_values.get('userId') is not None:
return queryset.filter(Q(user_id=info.variable_values.get('userId'))).order_by('-id')
return queryset
TrainingType
class TrainingType(gql_optimizer.OptimizedDjangoObjectType):
class Meta:
model = Training
fields = "__all__"
convert_choices_to_enum = False
It's possible to extend your types to add more fields that aren't in the Django model -- perhaps that is the technique you are looking for to inject more data into the query?
class TrainingType(gql_optimizer.OptimizedDjangoObjectType):
my_extra_field = graphene.Int() # for example
class Meta:
model = Training
fields = "__all__"
convert_choices_to_enum = False
You can also override the default resolvers that are created with DjangoObjectType.

How to filter a Django model based on requirement from another model?

I'd like to retrieve all my contacts from my Contact model excluding those listed on my DoNotContact model. Is the following the most efficient way to do that: contacts = Contact.objects.filter(dont_contact=False) Wondering if this is going to take long to process, is there a more efficient way?
class Contact(models.Model):
email = models.CharField(max_length=12)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
audience = models.ForeignKey(Audience, on_delete=models.CASCADE)
def dont_contact(self):
try:
get_object_or_404(DoNotContact, email=self.email)
return True
except:
return False
def __str__(self):
return self.email
class DoNotContact(models.Model):
email = models.CharField(max_length=12)
#views.py
def send_email(request):
if request.method == "POST":
contacts = Contact.objects.filter(dont_contact=False)
Kwargs used model queryset filter methods are resolved into database columns. dont_contact here is a method and doesn't exist as a column in Contact model so calling Contact.objects.filter(dont_contact=False) will raise a FieldError.
For current implementation of your models you can do following
dont_contacts = DoNotContact.objects.values('email')
contacts = Contact.objects.exclude(email__in=dont_contacts)
A better solution with higher performance is to remove DoNotContact and add a BooleanField to Contact which handles your requirement.

Django Rest Framework how to save a model with Related Field based on ID

I'm kind of new to DRF. I have Record model that looks like this:
class Records(models.Model):
owner = models.ForeignKey(User, null=True)
activity = models.ForeignKey(Activity, null=True)
time_start = models.DateTimeField(null=True)
time_end = models.DateTimeField(null=True)
...
The RecordSerializer is this one:
class RecordSerializer(serializers.ModelSerializer):
now = datetime.today()
owner = serializers.Field(source='owner.username')
time_start = serializers.DateTimeField(source='now')
class Meta:
model = Records
fields = ("owner", "activity", "time_start")
And this is the view:
class StartApiView(generics.CreateAPIView):
model = Records
serializer_class = RecordSerializer
def pre_save(self, obj):
obj.owner = self.request.user
The POST request is sent from Backbone and it includes a field with the activity id, for example "{activity:12}". What should I do if I want the view to save the Record and set the activity to the Activity with the id of 12?
The accepted answer was true for DRF v2.x but is no longer for newer (3.x) versions, as it would raise this AssertionError:
AssertionError: Relational field must provide a queryset argument, or set read_only=True.
For newer versions, just add the queryset argument to it:
class RecordSerializer(serializers.ModelSerializer):
activity = serializers.PrimaryKeyRelatedField(queryset=Activity.objects.all())
// [...]
Django REST Framework provides a PrimaryKeyRelatedField for exactly this use case.
class RecordSerializer(serializers.ModelSerializer):
activity = serializers.PrimaryKeyRelatedField()
owner = serializers.CharField(read_only=True, source='owner.username')
time_start = serializers.DateTimeField(source='now')
class Meta:
model = Records
fields = ("owner", "activity", "time_start")
This will produce output similar to what you are looking for, and it will accept the id of the activity when you want to update it.