Django ManyToManyField delete across models - django

I've the below inefficient 'destroy' method for deleting Ratings that are held in Stimulus which itself is held within Experiment (I have simplified my models, for reasons of clarity).
Could you advise on a more efficient way of achieving this?
class Rating(models.Model):
rater = TextField(null=True)
rating = FloatField(null=True)
created = models.DateTimeField(null=True)
class Stimulus(TimeStampedModel):
genes = TextField()
weights = ListField()
ratings = ManyToManyField(Rating, null=True)
evaluation = FloatField(null=True)
complete = BooleanField(default=False)
Class Experiment(models.Model):
all_individuals = ManyToManyField(Stimulus, null=True)
def destroy(self):
all_ratings = Rating.objects.all()
for ind in self.all_individuals.all():
ratings = ind.ratings.all()
for rating in ratings:
if rating in all_ratings:
Rating.objects.filter(id = rating.id).delete()
Background: I am using Django to run an experiment (Experiment) which shows Users many Stimuli (Stimulus). Each Stimulus gets rated many times. Thus, I need to save multiple ratings per stimulus (and multiple stimuli per experiment).

Some simple improvements
Remove the if rating in all_ratings, every rating will be in the list of all ratings
Do the delete on the database side
ind.ratings.all().delete()
Use prefetch_related to get the foreign key objects
self.all_individuals.prefetch_related('ratings'):
Combined would be:
def destroy(self):
for ind in self.all_individuals.prefetch_related('ratings'):
ratings = ind.ratings.all().delete()

I think that in this case using ManyToManyField isn't the best choice.
You'll have less problems using common ForeignKey's changing a little the structure of this models.
Eg.
class Rating(models.Model):
rater = TextField(null=True)
rating = FloatField(null=True)
created = models.DateTimeField(null=True)
stimulus = models.ForeignKey('Stimulus', related_name='ratings')
class Stimulus(TimeStampedModel):
genes = TextField()
weights = ListField()
#ratings = ManyToManyField(Rating, null=True)
evaluation = FloatField(null=True)
complete = BooleanField(default=False)
experiment = models.ForeignKey('Experiment', related_name='stimulus')
class Experiment(models.Model):
#all_individuals = ManyToManyField(Stimulus, null=True)
name = models.CharField(max_length=100)
This is a more clear structure and when you delete Experiment by, experiment_instance.delete() a delete cascade will delete all other related models.
Hope it helps.

Related

Aggregate in model property result in extra queries

My example:
class Product(models.Model):
name = models.CharField(max_length=50)
category = models.ManyToManyField("wms.ProductCategory", blank=True)
#property
def quantity_in(self):
return self.intodocumentproduct_set.aggregate(total=Sum('quantity_in', default=0))['total']
class IntoDocumentProduct(models.Model):
product = models.ForeignKey("wms.Product", on_delete=models.CASCADE)
quantity_in = models.FloatField(blank=True, null=True)
class ProductListAPIView(ListAPIView):
# queryset = Product.objects.prefetch_related('category').annotate(sum_quantity_in=Sum('intodocumentproduct__quantity_in', default=0)).all()
queryset = Product.objects.prefetch_related('category').all()
serializer_class = ProductModelSerializer
Commented queryset results in 4 queries while other query (using property) results in 6 queries. Probably n+ problem.
I have to use properties like: quantity_ordered,
quantity_reserved,
quantity_out,
quantity_in,
quantity_stock,
quantity_available,
quantity_pending_release and more in many places in web app. Calculating them in every view will be time comsuming and error susceptible.
This solution is not very handy when property is used in many views with many properties.
Is there any other solution to remove extra queries?

Get the average from list of values of foreign key object fields

I am making a movie rating app. Each movie can be rated by users.
class MovieRating(models.Model):
movie = models.OneToOneField(Movie)
users = models.ManyToManyField(User, through='UserRating')
average_rating = ???
or whichever is better
#property
def average_rating(self):
???
class UserRating(models.Model):
movie_rating = models.ForeignKey(MovieRating)
user = models.ForeignKey(User)
rating = models.PositiveIntegerField(
default=1,
validators=[
MinValueValidator(1),
MaxValueValidator(10)
]
)
review = models.TextField()
pub_date = models.DateTimeField(default=timezone.now)
class Meta:
unique_together = ('movie', 'user')
I want to get the average rating of for that movie. Meaning
average_rating = (Total rating by user for a MovieRating) / (Total users of MovieRating)
I can get the total users of a MovieRating by
movie_rating.users.count()
But how do I get the rating of every users in Total? Or else what can I do to get the average rating for a movie?
update:
Now I am doing this way, but I don't know whether this is the right way to go:
def average_rating(self):
if self.users.count() == 0:
return 0.0
rates = 0
rating_list = [user_rating.rating for user_rating in self.userrating_set.all()]
for r in rating_list:
rates = r + rates
return rates / self.users.count()
Django supports aggregation queries, including averaging. In your case you can aggregate on the related field through the object's reverse relation to its intermediary model. Since aggregations are performed natively by the database, they are generally recommended over manually calculating their values.
So in your average rating property method
from django.db.models import Avg
...
#property
def average_rating(self):
return self.userrating_set.aggregate(Avg('rating'))['rating__avg']
Try something like:
sum([rate.rating for rate in movie_rating.userrating_set.all()])

Django substract two fields from related model

With this models:
class Vine(models.Model):
autor = models.ForeignKey(Viner,related_name='autor')
titulo = models.CharField(max_length=450)
estado = models.CharField(choices=ESTADOS_VINE, max_length=30)
objects = models.Manager()
custom_object = managers.VineManager()
and the model for the votes
class Voto(models.Model):
user = models.ForeignKey(MyUser)
submit_date = models.DateTimeField(auto_now_add=True)
vine = models.ForeignKey(Vine)
valoracion = models.BooleanField(default=False)
and the class for the Favorites (This is working fine yet)
class Favorito(models.Model):
date = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='favoritos')
I have this 'query' in Django.
vines = Vine.custom_object.filter(estado=2).\
annotate(favoritosCount=Count('favoritos', distinct=True)).\
filter(voto__valoracion=False).annotate(disLikesCount=Count('voto', distinct=True))\
.annotate(likesCount=Count('voto', distinct=True)).filter(voto__valoracion=True)
But the second filter is not working because of the first.
Basically what I want is to get the sum of 'positive votes' - 'negative votes' as a field and order by it.
Could anyone please help me?
Thank you in advance
AFAIK you can't do that query with the ORM. You might be able to do it with a raw query.
I think It's easier if you add a count field to your Vine model and order by it. Then update that count field every time there's a new Voto.
Something like this:
from django.db.models import F
class Vine(models.Model):
...
votos = models.IntegerField()
class Meta:
ordering = ('votos',)
class Voto(models.Model):
...
def save(self):
"""When saving new Voto instance, update related Vine."""
if not self.pk:
new_vote = 1 if self.valoracion else -1
self.vine.update(votos=F('votos') + new_vote)
return super(Voto, self).save()
PS: If you want to know more about that F expression.

Django: Filter in multiple models linked via ForeignKey?

I'd like to create a filter-sort mixin for following values and models:
class Course(models.Model):
title = models.CharField(max_length=70)
description = models.TextField()
max_students = models.IntegerField()
min_students = models.IntegerField()
is_live = models.BooleanField(default=False)
is_deleted = models.BooleanField(default=False)
teacher = models.ForeignKey(User)
class Session(models.Model):
course = models.ForeignKey(Course)
title = models.CharField(max_length=50)
description = models.TextField(max_length=1000, default='')
date_from = models.DateField()
date_to = models.DateField()
time_from = models.TimeField()
time_to = models.TimeField()
class CourseSignup(models.Model):
course = models.ForeignKey(Course)
student = models.ForeignKey(User)
enrollment_date = models.DateTimeField(auto_now=True)
class TeacherRating(models.Model):
course = models.ForeignKey(Course)
teacher = models.ForeignKey(User)
rated_by = models.ForeignKey(User)
rating = models.IntegerField(default=0)
comment = models.CharField(max_length=300, default='')
A Course could be 'Discrete mathematics 1'
Session are individual classes related to a Course (e.g. 1. Introduction, 2. Chapter I, 3 Final Exam etc.) combined with a date/time
CourseSignup is the "enrollment" of a student
TeacherRating keeps track of a student's rating for a teacher (after course completion)
I'd like to implement following functions
Sort (asc, desc) by Date (earliest Session.date_from), Course.Name
Filter by: Date (earliest Session.date_from and last Session.date_to), Average TeacherRating (e.g. minimum value = 3), CourseSignups (e.g. minimum 5 users signed up)
(these options are passed via a GET parameters, e.g. sort=date_ascending&f_min_date=10.10.12&...)
How would you create a function for that?
I've tried using
denormalization (just added a field to Course for the required filter/sort criterias and updated it whenever changes happened), but I'm not very satisfied with it (e.g. needs lots of update after each TeacherRating).
ForeignKey Queries (Course.objects.filter(session__date_from=xxx)), but I might run into performance issues later on..
Thanks for any tipp!
In addition to using the Q object for advanced AND/OR queries, get familiar with reverse lookups.
When Django creates reverse lookups for foreign key relationships. In your case you can get all Sessions belonging to a Course, one of two ways, each of which can be filtered.
c = Course.objects.get(id=1)
sessions = Session.objects.filter(course__id=c.id) # First way, forward lookup.
sessions = c.session_set.all() # Second way using the reverse lookup session_set added to Course object.
You'll also want to familiarize with annotate() and aggregate(), these allow you you to calculate fields and order/filter on the results. For example, Count, Sum, Avg, Min, Max, etc.
courses_with_at_least_five_students = Course.objects.annotate(
num_students=Count('coursesignup_set__all')
).order_by(
'-num_students'
).filter(
num_students__gte=5
)
course_earliest_session_within_last_240_days_with_avg_teacher_rating_below_4 = Course.objects.annotate(
min_session_date_from = Min('session_set__all')
).annotate(
avg_teacher_rating = Avg('teacherrating_set__all')
).order_by(
'min_session_date_from',
'-avg_teacher_rating'
).filter(
min_session_date_from__gte=datetime.now() - datetime.timedelta(days=240)
avg_teacher_rating__lte=4
)
The Q is used to allow you to make logical AND and logical OR in the queries.
I recommend you take a look at complex lookups: https://docs.djangoproject.com/en/1.5/topics/db/queries/#complex-lookups-with-q-objects
The following query might not work in your case (what does the teacher model look like?), but I hope it serves as an indication of how to use the complex lookup.
from django.db.models import Q
Course.objects.filter(Q(session__date__range=(start,end)) &
Q(teacher__rating__gt=3))
Unless absolutely necessary I'd indeed steer away from denormalization.
Your sort question wasn't entirely clear to me. Would you like to display Courses, filtered by date_from, and sort it by Date, Name?

get all instances from related models

I have one problem
Looking below:
I have this model:
class Shoes(models.Model):
shop = models.ForeignKey(Store, related_name="%(class)s")
name = models.ForeignKey(ShoesItem)
size = models.ManyToManyField(ShoesSize, help_text=_("Get useful sizes"))
price = models.IntegerField()
in my case, I have models ShoesSize for store all ShoesSize and ShoesItem for store this Item
How can I get all sizes and all shops from ShoesItem instance?
there are (not full, for example):
class Store(models.Model):
name = models.CharField(max_length=255)
class Item(models.Model):
name = models.CharField(max_length=255)
brand = models.ForeignKey(Brand, related_name="%(app_label)s_%(class)s")
sysname = models.SlugField(max_length=255)
has_shop = models.BooleanField(editable=False, default=False)
description = models.TextField(blank=True, verbose_name="Описание")
color = models.ManyToManyField(Color, blank=True, related_name='%(app_label)s_%(class)s')
and I get list of instances of Item models. After that, I want to get all available sizes and shops for all kind of items
Use a reverse query by using the model name.
Get a ShoesItem:
shoes_item = ShoesItem.objects.all()[0]
Get ShoesSize objects for the ShoesItem via Shoes object:
sizes = ShoesSize.objects.filter(shoes__name=shoes_item)
Get Store objects for the ShoesItem via Shoes object:
shops = Store.objects.filter(shoes__name=shoes_item)
See more - Lookups that span relationships
For a queryset of ShoesItem:
shoes_items = ShoesItem.objects.filter(has_shop=True)
sizes = ShoesSize.objects.filter(shoes__name__in=shoes_items)
shops = Store.objects.filter(shoes__name__in=shoes_items)