Reputation: 355
I am relatively new to Django, so I am not sure if what I am asking possible.
I am building a website with functionality to rate users and write reviews about them. I have model for users (that has average rating field) and a model for reviews (with fields of author
, user_profile
, grade
and review
). I am using CreateView
for making reviews.
I am trying to do the following:
To make query to get all previous grades of that person (from Reviews
model).
Make calculations (sum all previous grades, add the new one and all that divide by number of grades (including new grade))
Save new average grade to UserProfile
model
Save review to Reviews
model
Redirect user to current detail view
Models.py
class UserProfile(models.Model):
...
avg_grade = models.FloatField(blank=True, null=True)
...
class Reviews(models.Model):
user_profile = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
grade = models.PositiveIntegerField()
review = models.CharField(max_length=256, null=True, blank=True)
author = models.CharField(max_length=256)
In views.py
I managed to make query of grades of that user, but not sure where to do calculations for new average grade (if this is possible inside a Class-Based-View):
class CreateReview(LoginRequiredMixin, CreateView):
form_class = Forma_recenzije
success_url = reverse_lazy('detail')
template_name = 'accounts/recenzija.html'
def get_queryset(self):
u = UserProfile.objects.get(id=int(self.kwargs['pk']))
return Reviews.objects.filter(user_profile=u)
def form_valid(self, form):
form.instance.author = self.request.user
form.instance.user_profile = UserProfile.objects.get(id=int(self.kwargs['pk']))
return super(CreateReview, self).form_valid(form)
urlpatterns:
[...
url(r'^dadilje/(?P<pk>[-\w]+)/$', views.DadiljaDetailView.as_view(), name="detail"),
url(r'^dadilje/(?P<pk>[-\w]+)/recenzija$', views. CreateReview.as_view(), name="recenzije")
...
]
Upvotes: 0
Views: 1400
Reputation: 308809
You can calculate the new average once you have called super()
, before you return the response.
def form_valid(self, form):
form.instance.author = self.request.user
user_profile = UserProfile.objects.get(id=int(self.kwargs['pk']))
form.instance.user_profile = user_profile`
response = super(CreateReview, self).form_valid(form)
avg_grade = Review.objects.filter(user_profile=user_profile).aggregate(Avg('grade'))['grade__avg']
user_profile.avg_grade = avg_grade
user_profile.save()
return response
Or, if you find that calling super()
makes it hard to see what's going on, you could explicitly save the form and redirect instead:
def form_valid(self, form):
form.instance.author = self.request.user
user_profile = UserProfile.objects.get(id=int(self.kwargs['pk']))
form.instance.user_profile = user_profile`
review = form.save()
avg_grade = Review.objects.filter(user_profile=user_profile).aggregate(Avg('grade'))['grade__avg']
user_profile.avg_grade = avg_grade
user_profile.save()
return HttpResponseRedirect(self.success_url)
Note that you may not have to store the avg_grade
on the user profile -- you could use annotate
to calculate the averages when you need them.
Upvotes: 1
Reputation: 12849
For the kind of things you want to do Django has signals which you can listen out for.
As an example, you could have a function that listens out for when UserProfile
has been saved which clears the cache keys related to that profile.
These functions are usually added to a signals.py
within your apps, or in models.py
files after you've defined your model.
Signals have to be loaded after your models, so if using a signals.py
the way I tend to do it is in apps.py
;
class MyAppConfig(AppConfig):
"""App config for the members app. """
name = 'my app'
verbose_name = _("My App")
def ready(self):
""" Import signals once the app is ready """
# pylint: disable=W0612
import myapp.signals # noqa
Here's an example of a signal receivers, pre_save
happens just before the object is saved, so you could run your calcs at this point;
@receiver(pre_save, sender=UserProfile)
def userprofile_pre_save(sender, instance, **kwargs):
"""
Calc avg score
"""
reviews = Reviews.objects.filter(user_profile=instance).aggregate(Avg('grade'))
instance.avg_grade = reviews['grade_avg']
You'd probably want your receiver on the Review
change, but the above was an easy example!!
If you're new to django this might be a bit complex, but give this a read; https://simpleisbetterthancomplex.com/tutorial/2016/07/28/how-to-create-django-signals.html
Upvotes: 1
Reputation: 810
I think best solution here is use django signals
As result you separate view and domain logic and your calculations will apply after each change (not only changes in view)
Also if your calculation will take a lot of time you may easily move this functionality in async job (e.g. celery )
Upvotes: 0