I working on an app using django and python at school. It an app for writing reviews and rating movies. I would like to implement a rating system, so I can display the average rating for a film based on the rating given in the reviews. At the moment this code gives the rating in descending order for all reviews, and not the average. Like if I got two reviews for a movie, with score 1 and 5, I get both, but not one that says 3. Please help!
In models.py:
class Film(models.Model):
title = models.CharField(max_length=100)
title_short = models.CharField(max_length=17, default=None, null=True)
plot = models.TextField()
poster = models.ImageField(default="default.png", upload_to="posters")
release_date = models.DateField(blank=True, null=True)
date_posted = models.DateTimeField(default=timezone.now)
class Review(models.Model):
writer = models.ForeignKey(User, on_delete=models.CASCADE)
reviewed_film = models.ForeignKey(Film, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
content = models.TextField()
rating = models.IntegerField(
default=1, validators=[MinValueValidator(1), MaxValueValidator(5)]
)
In views.py:
class ToplistListView(LoginRequiredMixin, ListView):
model = Review
template_name = "board/toplist.html"
context_object_name = "films"
def get_queryset(self):
return Review.objects.annotate(avg_rating=Avg('rating')).order_by('-avg_rating')
You're getting the average rating on each review, which will of course just give you the rating for that review as each review only has a single rating. The average of a single number is just that number.
I think what you want is the average for all the ratings for a film. So you'd want a query that looks something like:
Flim.objects.annotate(avg_rating=Avg('review_set__rating')).order_by('-avg_rating')
Also, you can change the name of review_set (which is default for a Foreign Key lookup if no related name is set) to whatever you want by setting related_name='reviews' or whatever you want it to be called on the FK definition.
So:
reviewed_film = models.ForeignKey(Film, related_name='reviews', on_delete=models.CASCADE)
ETA: if you make that change, your query would become:
Flim.objects.annotate(avg_rating=Avg('reviews__rating')).order_by('-avg_rating')
Related
i have a model course and i also have a model review and rating that allows users rate a particular course. now i want to count all the ratings on each course when i filter. NOTE: in the courses detail view i figured out a way to count the ratings like this rating_count = CourseRating.objects.filter(course=course, rating__in=["3.0", "4.0", "5.0"]).count(). This only worked in the detail view because i get the course first using course = Course.objects.get(slug=course_slug).
Now i want to count the rating in course lists view, how can i do this while using filter?
this is how the detail view looks like
#login_required
def course_details(request, course_slug):
user = request.user
course = Course.objects.get(slug=course_slug)
reviews_count = CourseRating.objects.filter(active=True, course=course).count()
rating_count = CourseRating.objects.filter(course=course, rating__in=["3.0", "4.0", "5.0"]).count()
this is how the list view look like NOTE: this is where i want to count all the rating of each course
def index(request):
courses = Course.objects.filter(course_publish_status="published").order_by('?')
models.py
class Course(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
course_title = models.CharField(max_length=100, null=True, blank=True)
slug = models.SlugField(unique=True)
course_category = models.ForeignKey(Category, on_delete=models.DO_NOTHING, null=True, blank=True, related_name="courses")
course_publish_status = models.CharField(max_length=10000, choices=COURSE_PUBLISH_STATUS, default="in_review")
class CourseRating(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
course = models.ForeignKey(Course, on_delete=models.CASCADE, null=True, related_name="ratings")
rating = models.CharField(max_length=1000, choices=USER_COURSE_RATING)
review = models.TextField()
date = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(default=False)
def __str__(self):
return f"{self.course.course_title} - {self.rating}"
class Meta:
verbose_name = "Course Reviews and Ratings"
What you're looking for is Aggregation the docs have some pretty good (and quick) examples of how to achieve it.
You're looking to use an .annotate with the Count object. Judging by your filtering for ratings, you'll also want to use the filter= parameter of the Count object.
I also highly suggest you look into how to properly use the Q() object.
With all that being said, an example to achieve what you're looking for:
courses = Course.objects.filter(
course_publish_status="published",
).annotate(
rating_count=Count(
'ratings',
filter=Q(ratings__rating__in=["3.0", "4.0", "5.0"])
)
).order_by("?")
Keep in mind that I considered you have a related_name="ratings" on your ForeignKey. For your next questions I strongly suggest sharing the models you're working with as well (or at least the relevant portions of them).
I am currently trying to organize a django database model for an online shop-system with users and products.
My code:
class UserData(models.Model):
username = models.CharField(max_length=100)
password = models.CharField(max_length=500)
bought_products = models.ForeignKey(MarketProducts, on_delete=models.CASCADE)
class VendorData(models.Model):
username = models.CharField(max_length=100)
password = models.CharField(max_length=500)
sold_products = models.ForeignKey(MarketProducts, on_delete=models.CASCADE)
class MarketProducts(models.Model):
category = models.CharField(max_length=100)
vendor = models.ForeignKey(VendorData, on_delete=models.CASCADE)
name = models.CharField(max_length=200)
description = models.CharField(max_length=1000)
price = models.IntegerField()
pub_date = models.DateTimeField('Date published')
image = models.ImageField(upload_to=b'shop/media/images/')
likes = models.IntegerField()
dislikes = models.IntegerField()
How can I organize a good working system so all the products a user bought are saved inside the bought_products column and all the products a vendor sold can be saved inside the sold_products column. Do I have to use a ForeignKey for that or is there something more suitable for this situation? Also, if there is anything unwise about the existing structure of the database model (for example the current image field column only saves the link but not the image itself which is kinda weird...), please feel free to correct me :).
Many thanks in advance :D
In this case I suggest to make bought_products and sold_products instances of ManyToManyField because the same product can be bought by multiple Users and sold by multiple vendors
Firstly I would start by reading the following documentation on django auth customization. Seems like that would help you out a little bit.
https://docs.djangoproject.com/en/2.2/topics/auth/customizing/
Additionally, I think you need to better evaluate your data modelling to make each model more explicit/clearly defined. See example below:
class Products(models.Model):
vendor = models.ForeignKey(VendorData, on_delete=models.CASCADE)
name = models.CharField(max_length=200)
description = models.CharField(max_length=1000)
price = models.IntegerField()
pub_date = models.DateTimeField('Date published')
image = models.ImageField(upload_to=b'shop/media/images/')
likes = models.IntegerField()
dislikes = models.IntegerField()
class Category(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=100)
active = models.BooleanField(default=True)
class ProductCategory(models.Model):
product = models.ForeignKey(Products, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
creation_date = models.DateTimeField(auto_add_now=True)
From there I would construct a separate model which would store the customer purchased items. Since you already have a model which stores the vendor to product data, you shouldn't need anything additional to identify how many sales a particular vendor has.
I have models of Exercise, Training and Workout.
Training contains some exercises (Exercise)
Workout contains trainings (Training).
Snippet of my models.py:
class Exercise(models.Model):
user = models.ForeignKey(User, related_name='exercises',
on_delete=models.CASCADE)
name = models.CharField(max_length=80)
description = models.TextField(max_length=300)
details = models.ManyToManyField(ExerciseDetail, blank=True)
...
class Training(models.Model):
user = models.ForeignKey(User, related_name='trainings',
on_delete=models.CASCADE)
name = models.CharField(max_length=80)
description = models.CharField(max_length=250)
exercises = models.ManyToManyField(Exercise, related_name='trainings',
blank=True)
...
class Workout(models.Model):
user = models.ForeignKey(User, related_name='workouts',
on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now=True)
name = models.CharField(max_length=200)
description = models.TextField(max_length=400, blank=True)
trainings = models.ManyToManyField(Training, related_name='workouts',
blank=True)
...
I would like to have possibility to use something like Workout.objects.get(name='workout').exercises.objects.all() to get a list/set of all exercises included in trainings of chosen Workout.
I would also like to have possibility to use exercises`` field with Django Rest Framework to list all exercises, possibly with link to particularExercise``` model serializer.
Can someone give a hint how can I do that?
You can query this with:
Exercise.objects.filter(
trainings__workouts__name='workout'
)
With the consecutive underscores (__), you thus can look "through" relations.
This will thus return the Exercises that belong to Trainings that belong to Workouts with as name 'Workout'.
As can be seen in the code below, I have an intermediate model that defines a custom date field. How can I reference the date_assigned field in the relation model. I am trying find the number of tenders that were assigned to the "user" (i.e. CompanyProfile) on a particular date as per the date_assigned field in the relation model. The CompanyProfile (i.e. user) model and the Tender model share one thing in common, they both have a relationship with the Keywords model through a ManyToMany relationship. This is how I am able to find Tenders allocated to the CompanyProfile through the Keywords model.
This is the final result I am aiming for.
class CompanyProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
accountNumber = models.CharField(max_length=25, default=1, blank=False, null=False)
companyName = models.CharField(max_length=200, blank=False)
companyRegNum = models.CharField(max_length=30, blank=True)
contactNumber = models.CharField(max_length=20, blank=False)
address = models.CharField(max_length=300, blank=True)
areaCode = models.CharField(max_length=10, blank=False)
deliveryEmails = models.TextField(blank=True) #this is the list of all the people chosen to recieve daily notification.
tenderCategory = models.ManyToManyField(Category, blank=False) #links the user to the chosen category.
provinces = models.ManyToManyField(Province, blank=False) #links the user to the chosen Provinces.
package = models.ForeignKey(Packages, default=1, blank=False) #links the user to the chosen package.
pymntMethod = models.IntegerField(blank=True, default=3) #this is the chosen payment method (e.g credit card=1, debit order=2 or direct debit=3)
keywords = models.ManyToManyField(Keywords) #links the user to the chosen keywords.
extraKeywords = models.TextField(default='', blank=True) #this field acts as a container of extra keywords from the user. These are keywords that we do not have in our database.
contractDuration = models.IntegerField(blank=False, default=12)
termsAndConditions = models.BooleanField(blank=False, default=1) #this is the T&C's field that must be agreed to by the client.
commencementDate = models.DateTimeField(default=timezone.now, blank=True)
class Keywords(models.Model):
keyword = models.CharField(max_length=150)
class Meta:
verbose_name_plural = ('Keywords')
ordering = ['keyword', ]
def __str__(self):
return self.keyword
#This is the model that stores the tender.
class Tender(models.Model):
tenderCategory = models.ManyToManyField(category, blank=False) #this field holds the tender category, e.g. construction, engineering, human resources etc.
tenderProvince = models.ForeignKey(Province, default=1, blank=False) #this is the province the tender was advertised from.
keywordTags = models.TextField(blank=False) #this field holds keywords for the tender as per the tender title or as determined by the tender capturer.
buyersName = models.CharField(max_length=100) #this is the name of the Buyer e.g. Dept. of Transport, Transnet, Dept of Agriculture etc.
summary = models.TextField(blank=False) #this is the tender title as per the Buyer.
refNum = models.CharField(max_length=100) #tender ref number as per the Buyer.
issueDate = models.DateTimeField(blank=True, null=True) #date the tender was published
closingDate = models.DateTimeField(blank=True, null=True) #tender closing date
siteInspection = models.TextField(blank=True, null=True) #site inspection date, if any
enquiries = models.TextField(blank=True, null=True) #this field stores details of the contact person, for the tender.
description = models.TextField(blank=True, null=True) #this is the body of the tender. the tender details are captured here.
assigned_keywords = models.ManyToManyField(Keywords, blank=True, through='tenderKeywords')
matched = models.BooleanField(default=0, blank=False)
capture_date = models.DateField(default=timezone.now, blank=False, null=False)
class TendersKeywords(models.Model):
tender = models.ForeignKey(tender, related_name='tender_keywords')
keyword = models.ForeignKey(Keywords, related_name='tender_keywords')
date_assigned = models.DateField(default=timezone.now, blank=False, null=False)
I am able to find the associated tenders for the CompanyProfile but I have a problem aggregating the results base on the tender issued_date which is defined in the relation model TendersKeywords.
The expected result you show is a model aggregation query. In particular, computing a count of matches grouped by a particular field, is a good use of the Count aggregation, applied to the queryset with an annotate operation.
import django.db
Tender.objects.values('issued').annotate(
tenders=django.db.models.Count('issued'))
What that does:
Use the Tender model manager (the Tender.objects attribute);
Use the values method to make a queryset containing just the issued field value;
Annotate that queryset with a field named tenders, that:
Has the value computed by the Count aggregate function for each group.
That all assumes a coherent model schema; I had to make some guesses (your code as presented doesn't work, please review the guidelines on creating a minimal, complete, verifiable example. In brief: Strip the example down to the minimum needed, and then actually run it to make sure it'll work for us too).
If you have an existing query and want to annotate that, then just apply the same technique:
existing_queryset.values('tender_issued').annotate(
tenders=django.db.models.Count('tender_issued'))
where tender_issued is whatever field in your queryset contains the value you want to group and count.
First, I want to modify your models
class tenderKeywords(models.Model):
tender = models.ForeignKey(tender, related_name='tenderkeywords')
keyword = models.ForeignKey(Keywords, related_name='tenderkeywords')
date_assigned = models.DateField(default=timezone.now, blank=False, null=False)
then:
count = tender.objects.filter(tenderKeywords__keyword__Keywords="", tenderKeywords__date_assigned= datetime)
Python developers have some standard in writing code. for example first letter of model's name should be capital. thanks for this.
Edited Title : Limit multiple "Many to One" fields into "One to One" association : Django
We've Book, User and Rating as Django model.
Each Book has many Ratings
Each Rating has one Book
Each User has many Ratings
Each Rating has one User
For Book
class Book(models.Model):
isbn = models.CharField(max_length=32)
title = models.CharField(max_length=256)
def __str__(self):
return self.title
For Book Rating
class BookRating(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
rating = models.SmallIntegerField(choices=[(i, i) for i in range(1, 6)])
def __str__(self):
return self.rating
Problem Statement
How can I ensure that each User has atmost one rating on each book?
Just do
class BookRating(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
rating = models.SmallIntegerField(choices=[(i, i) for i in range(1, 6)], default=1)
class meta:
unique_together = ('book','user')
Your models do implement a many to many relationship, however you are not getting full access to the django ManyToMany functionality. I recommend you do something like this:
class Book(models.Model):
isbn = models.CharField(max_length=32)
title = models.CharField(max_length=256)
ratings = models.ManyToManyField(User,through='BookRating')
When you do this your BookRating can remain unchanged, but the small change to the Book model gives you full access to the api described here: https://docs.djangoproject.com/en/1.10/topics/db/examples/many_to_many/
What's interesting is that modifying the Book model as described above does not make any changes to your table structures in your database. They remain unchanged. It's simply a matter of unlocking the api.