How to get choices to work with Django REST framework - django

I have a model:
class Film(models.Model):
title = models.CharField(max_length=250)
starring = models.CharField(max_length=250)
description = models.CharField(max_length=500)
rating = models.CharField(max_length=2, choices=(('1','U'),('2','PG'),('3','12A'),('4','12'),('5','15'),('6','18')),default='18')
length = models.IntegerField()
def __str__(self):
return f"{self.title}, {self.rating}"
and a serialiser:
class FilmSerializer(serializers.ModelSerializer):
class Meta:
model = Film
fields = ('title','description','starring','rating','length')
def to_representation(self, instance):
data = super().to_representation(instance)
hours = math.floor(int(data['length']) / 60)
minutes = int(data['length']) % 60
data['length'] = f"{hours}h {minutes}m"
return data
and an api view:
class FilmList(ListAPIView):
queryset = Film.objects.all()
serializer_class = FilmSerializer
filter_backends = (DjangoFilterBackend,)
filterset_fields = ('rating',)
When i use the Django Rest frame work I can filter by the rating, but only if i remove the choices definition from the model. When the choices definition is present on the 'rating' then the filter returns nothing at all.
I would actually like to use that to filter on the 'rating.' Is there a way round this?
thanks

Related

How to customize showing query parameter with DRF filtering (ManyToManyfield)

I hope my question is not a duplicated..
I'm building a rest API with DRF, now I'm trying to do filtering hashtag.
The url I want is article/list?hashtags=dobi
BUT I got id, like article/list?hashtags=13
Here is models.py:
class Article(AbstractTimeStamp):
user = models.ForeignKey('users.User', on_delete=models.CASCADE)
title = models.CharField(max_length=300,blank=False)
content = models.TextField(max_length=1000)
hashtags = models.ManyToManyField('Hashtag', max_length=200, related_name='articles', blank=True)
class Hashtag(models.Model):
hashtag = models.CharField(max_length=200, unique=True, blank=True)
Here is my serializers.py:
class ListSerializer(serializers.ModelSerializer):
hashtags = HashtagsSerializer(many=True)
class Meta:
model = Article
fields = ['id','title','hashtags']
Here is my views.py:
class ArticleListAPIView(generics.ListAPIView):
queryset = Article.objects.all()
serializer_class = ListSerializer
filter_backends = [filters.SearchFilter,DjangoFilterBackend]
search_fields = ['title']
filterset_fields = ['hashtags']
How can I override it?
I tried to overriding in listapiview
lookup_url_kwarg = ['hashtags']
but it didn't work.
I think you need to create a new Filterset for filtering the field hashtag.
class HashTagFilter(filters.FilterSet):
hashtags = CharFilter(method='filter_hashtags')
def filter_hashtags(self, queryset, name, value):
lookup = '__'.join([name, 'hashtag'])
return queryset.filter(**{lookup: value})
class Meta:
model = Article
fields = ['hashtags']
And in ArticleListAPIView, you need to set filterset_class.
class ArticleListAPIView(generics.ListAPIView):
queryset = Article.objects.all()
serializer_class = ListSerializer
filter_backends = [filters.SearchFilter,DjangoFilterBackend]
filterset_class = HashtagFilter
search_fields = ['title']

Is there a way to reload ViewSet on each GET request for new data in DRF?

I am trying to generate a random object from my Model. The problem is that it will only work one time, then I have to restart the server to get a new object. It just keeps giving me the same object until the restart.
I have been looking for solution on stack overflow but haven't found any.
Views.py
def dailyaskist(category):
qs = Task.objects.filter(category=category)
max_num = len(qs)
while True:
pk = random.randint(1, max_num)
task = Task.objects.filter(pk=pk).first()
if task:
return task.pk
class DailyTaskEcommerceViewSet(viewsets.ModelViewSet):
category = 'ecommerce'
task_pk = dailyaskist(category)
queryset = Task.objects.filter(pk=task_pk)
serializer_class = TaskSerializer
serialisers.py
class StepSerializer(serializers.HyperlinkedModelSerializer):
task_id = serializers.PrimaryKeyRelatedField(queryset=Task.objects.all(), source='task.id')
class Meta:
model = Step
fields = ('title', 'description', 'done', 'task_id')
class TaskSerializer(serializers.HyperlinkedModelSerializer):
steps = StepSerializer(many=True, read_only=True)
class Meta:
model = Task
fields = ('title', 'description', 'video', 'done', 'steps')
models.py
Categories = (
('ecommerce', 'Ecommerce'),
)
class Task(models.Model):
title = models.CharField(max_length=50)
description = models.TextField(max_length=360)
video = models.CharField(max_length=30, default='')
category = models.CharField(choices=Categories, default='', max_length=30)
done = models.BooleanField(default=False)
def __str__(self):
return self.title
class Step(models.Model):
task = models.ForeignKey(Task, related_name='steps', on_delete=models.CASCADE)
title = models.CharField(max_length=50)
description = models.TextField(max_length=360)
done = models.BooleanField(default=False)
def __str__(self):
return self.title
I want to receive a new object (task) each time I make a GET request using the DailyTaskEcommerceViewSet.
Thanks in advance! :D
You would do this in a method. In this case, get_queryset seems the right place.
class DailyTaskEcommerceViewSet(viewsets.ModelViewSet):
serializer_class = TaskSerializer
category = 'ecommerce'
def get_queryset(self):
task_pk = dailyaskist(self.category)
return Task.objects.filter(pk=task_pk)

Django list data from dfiferent models in List View with correct ordering

Heys guys.. I am making a Django project which is a simple clone of Twitter.. Got the idea from Justin Mitchell's Udemy course..
So i implemented a Tweet model and a Retweet model which has ForeignKey to the original Tweet and the User..
The thing is that in the homepage i want both the Tweets and Retweets to show and in the order they were created..
I am using Django Rest Framework for the CRUD functionality of Tweet using ModelViewSet
Any idea on how i achieve that using Rest Framework or if that isn't possible could you please give me some other idea..
Thank you in advance..
models.py
class Tweet(models.Model):
content = models.CharField(max_length=140)
user = models.ForeignKey(User, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
class Meta:
ordering = "-created_on", "content", "user",
def __str__(self):
return self.content
def get_absolute_url(self):
return reverse("tweet_api:tweet-detail", args=[self.id])
class Retweet(models.Model):
tweet = models.ForeignKey(Tweet, on_delete=models.CASCADE, related_name="retweet")
user = models.ForeignKey(User, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = "-created_on", "user",
def __str__(self):
return self.tweet.content
serializers.py
class TweetSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
created_on = serializers.SerializerMethodField()
date_display = serializers.SerializerMethodField()
class Meta:
model = models.Tweet
fields = "id", "content", "created_on", "date_display", "user",
def get_created_on(self, obj):
return obj.created_on.strftime("%I:%M %p - %d %b %Y")
def get_date_display(self, obj):
obj_date = obj.created_on
days = (timezone.datetime.now() - obj_date).days
if days > 0:
return obj_date.strftime("%d %b")
else:
return naturaltime(obj_date)
class RetweetSerializer(serializers.ModelSerializer):
tweet = TweetSerializer()
user = UserSerializer(read_only=True)
date_display = serializers.SerializerMethodField()
class Meta:
model = models.Retweet
fields = "id", "tweet", "user", "created_on", "date_display",
def get_date_display(self, obj):
obj_date = obj.created_on
days = (timezone.datetime.now() - obj_date).days
if days > 0:
return obj_date.strftime("%d %b")
else:
return naturaltime(obj_date)
views.py
class TweetViewSet(ModelViewSet):
serializer_class = serializers.TweetSerializer
queryset = models.Tweet.objects.all()
pagination_class = DefaultPagination
filter_backends = filters.SearchFilter,
search_fields = "content", "user__username", "user__first_name", "user__last_name",
def perform_create(self, serialiazer):
return serialiazer.save(user=self.request.user)
class RetweetViewSet(ModelViewSet):
serializer_class = serializers.RetweetSerializer
queryset = models.Retweet.objects.all()
pagination_class = DefaultPagination
filter_backends = filters.SearchFilter,
search_fields = "tweet__content", "user__username", "user__first_name", "
As Tweet and Retweet data reside on completely different models, what you want to do is not simple with your current model structure. To combine them in a single view, you'd need to override many things on ViewSet, and do the operations like sorting in-memory, which would not be scalable. One possible solution could be to use model inheritance, have a base model for both Tweet and Retweet, and build your serialzier and view set on that model. A model structure like the following could be used:
class Post(models.Model):
"""
Base model for user posts
"""
user = models.ForeignKey(User, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
class Tweet(Post):
content = models.CharField(max_length=140)
updated_on = models.DateTimeField(auto_now=True)
class Retweet(Post):
tweet = models.ForeignKey(Tweet, on_delete=models.CASCADE, related_name="retweet")
With these models in-place, you can create a PostSerializer and PostViewSet, and use these only for listing posts, you can keep using Tweet and Retweet views and serializers for creating and updating.

Django REST Framework : serializer fields are missing in the response

I'm using Django 2.0 and Django REST Framework.
I have two models contact and transaction as below
contact model
class Contact(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100, blank=True, null=True)
amount given model
class AmountGiven(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.PROTECT)
amount = models.FloatField(help_text='Amount given to the contact')
interest_rate = models.FloatField(blank=True, default=None, null=True, help_text='% of interest to be calculated')
_given_date = models.DateTimeField(
db_column='given_date',
default=timezone.now,
help_text='Date and time when amount was given to the contact'
)
def __str__(self):
return str(self.amount)
#property
def given_date(self):
return self._given_date
#given_date.setter
def given_date(self, value):
self._given_date = value
#property
def interest_to_pay(self):
if self.interest_rate:
datetime_diff = datetime.now(get_localzone()) - self.given_date
days = datetime_diff.days
duration_in_year = days/365
simple_interest_amount = (self.amount * duration_in_year * self.interest_rate)/100
return simple_interest_amount
return 0
#property
def total_payable(self):
return self.amount + self.interest_to_pay
#property
def amount_due(self):
returned_amount = 0
for returned in self.amountreturned_set.all():
returned_amount += returned.amount
return self.total_payable - returned_amount
and ContactSerializer
class ContactSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedRelatedField(
view_name='contacts:detail',
read_only=True
)
user = serializers.CurrentUserDefault()
amount_due = ReadOnlyField(source='amountgiven__amount_due')
class Meta:
model = Contact
fields = ('url', 'id', 'first_name', 'last_name', 'full_name', 'amount_due')
and in views.py
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)
def get_queryset(self):
return Contact.objects.filter(user=self.request.user)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
But there is no field as amount_due and url in the response returned while making the request to /contacts/ endpoint with GET method.
Based on your comment, you want the sum of all the amounts(please edit your question). so you should use annotate in your queryset:
from django.db.models import Sum
def get_queryset(self):
return Contact.objects.filter(user=self.request.user).annotate(amount_due=Sum('amountgiven_set__amount'))
(I recommend using modelManager for the queryset and the filtering instead of doing it here)
and add a field like this to your serializer:
amount_due = serializer.IntegerFiled()
Your modeling doesn't allow you to access amount_due in the way which you'd like.
Your Contact model does not have amountgiven attribute.
It does however have amountgiven_set which you can use to obtain a queryset of amountgiven for given contact.
But there can be multiple ones so you need to decide which amount_due you want to display in your serializer.
You can use SerializerMethodField to serializer the value of amount_due which you would like:
class ContactSerializer(serializers.HyperlinkedModelSerializer):
amount_due = serializers.SerializerMethodField()
def get_amount_due(self, obj):
amountgiven = obj.amountgiven_set.first()
return amountgiven.amount_due
But again, as i already mentioned - amountgiven_set returns a queryset where you can have multiple objects.
In case you are sure you have only one for given contact, you can use first() as in my example to get it.

How to send info in URL?

I am trying to create a product filter.
I am sending the user choice in URL
if the user select size = L then using request.GET
I am receiving:
{'size': ['L']}
But I want to receive: {'size':{'op':'in','attri':'L'}}
Is this possible?
Please help
my models are
class ProductAttribute(models.Model):
slug = models.SlugField(max_length=50, unique=True)
name = models.CharField(max_length=100)
op = models.CharField(max_length=20,default='in')
class Meta:
ordering = ('slug', )
def __str__(self):
return self.name
def get_formfield_name(self):
return slugify('attribute-%s' % self.slug, allow_unicode=True)
def has_values(self):
return self.values.exists()
class AttributeChoiceValue(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(max_length=100)
attribute = models.ForeignKey(
ProductAttribute, related_name='values', on_delete=models.CASCADE)
class Meta:
unique_together = ('name', 'attribute')
def __str__(self):
return self.name
class Product(models.Model):
name = models.CharField(max_length=128)
attributes = HStoreField(default={})
q2 = AttributeChoiceValue.objects.filter(attribute__name='size')
My size filter(filter.py) is:
size = django_filters.ModelMultipleChoiceFilter(queryset=q2.values_list('name', flat=True).distinct(),widget=forms.CheckboxSelectMultiple)
I am currently using the following query to filter my database in views.py
result = Product.objects.all()
for key, value in request.GET:result = result.filter(**{'attributes__{}__in'.format(key): value})
I want to make it
a=request.GET
for key, value in a:
result = result.filter(**{'attributes__{}__{}'.format(key,a['op']): value})
so that if I even use Price range as filter my query filter accordingly will be
attributes__price__range
You can send info to your views via "path converters":
https://docs.djangoproject.com/en/2.0/topics/http/urls/#path-converters
Or using regular expressions:
https://docs.djangoproject.com/en/2.0/topics/http/urls/#using-regular-expressions