Functions under django model class - django

I'm working on django models. At the design level I'm confused if I can implement functions inside model class. If I can implement then what kind of functions should be going inside and what kind of functions shouldn't. I couldn't find a document regarding this apart from doc
Or is there any document where I can figure out about this?

Yes, of course you can create functions inside the model class. It's highly recommended especially for things that you have to calculate specifically for objects of that model.
In example it's better to have function that calculates let's say Reservation time. You don't have to put that info inside database, just calculate only when it's needed:
class Reservation(models.Model):
valid_to = models.DateTimeField(...)
def is_valid(self):
return timezone.now() < self.valid_to
Depending on what you actually need/prefer it might be with #property decorator.

I guess you are asking about the old discussion "Where does the business logic go in a django project? To the views, or the model?"
I prefer to write the business logic inside of the views. But if it happens that I need a special "treatment" of a model several times in multiple views, I turn the treatment inside of the model.
To give you an example:
# models.py
class Customer(models.Model):
name = models.CharField(max_length=50, verbose_name='Name')
# views.py
def index(request):
customer = Customer.objects.all().first()
name = str.upper(customer.name) # if you need that logic once or twice, put it inside of the view
return HttpResponse(f"{name} is best customer.")
If you need the logic in multiple views, over and over again, put it inside of your model
# models.py
class Customer(models.Model):
name = models.CharField(max_length=50, verbose_name='Name')
#property
def shouted_name(self):
return str.upper(self.name)
# views.py
def index(request):
customer = Customer.objects.all().first() # grab a customer
return HttpResponse(f"{customer.shouted_name} is best customer.")
def some_other_view(request):
customer = Customer.objects.all().first() # grab a customer
customer_other = Customer.objects.all().last() # grab other customer
return HttpResponse(f"{customer.shouted_name} yells at {customer_other}")
# see method shouted_name executed in two views independently

Related

Make Django ORM automatically fetch properties

Say I have a Post model like this:
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
text_content = models.CharField()
#property
def comment_count(self):
return self.comments.count()
Say I need to get all the data for the post with ID 3. I know that I can retrieve the user along with the post in one query, by doing Post.objects.select_related('user').get(pk=3), allowing me to save a query. However, is there a way to automatically fetch comment_count as well?
I know I could use Post.objects.annotate(comment_count=Count('comments')) (which will require me to remove the property or change the name of the annotation, as there would be an AttributeError), however is it possible to make the ORM do that part automatically, since the property is declared in the model?
Although I could just add the .annotate, this can get very tedious when there are multiple properties and foreign keys that need to be fetched on multiple places.
Maybe this is not a perfect solution for you but you might find something similar that works. You could add a custom object manager, see docs
Then you could add a method like with_comment_count(). OR if you always want to annotate, then you can modify the initial queryset.
class PostManager(models.Manager):
def with_comment_count(self):
return self.annotate(
comment_count=Count('comments')
)
# Or this to always annotate
def get_queryset(self):
return super().get_queryset().annotate(
comment_count=Count('comments')
)
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
text_content = models.CharField()
objects = PostManager()
Then you could query like this.
post = Post.objects.get(pk=1).with_comment_count()
post.comment_count
Thanks to Felix Eklöf I managed to get it to work! As specified in the question, this was in the case in which there were multiple properties/foreign keys that need prefetching, and as such the ability to 'chain' prefetching was needed. This isn't possible by chaining PostManager methods, since they don't return a PostManager but a QuerySet, however I came up with a function that caches whatever it is asked to cache all at once, avoiding the need for chaining methods:
class PostManager(models.Manager):
def caching(self, *args):
query = self.all()
if 'views' in args:
query = query.annotate(view_count=Count('views'))
if 'comments' in args:
query = query.annotate(comment_count=Count('comments'))
if 'user' in args:
query = query.select_related('user')
if 'archives' in args:
query = query.prefetch_related('archives')
return query
class Post(models.Model):
objects = PostManager()
...
# caching can then be added by doing, this, for instance:
Post.objects.caching('views', 'user', 'archives').get(id=3)
Note that for this to work, I also had to change #property to #cached_property, thus allowing the ORM to replace the value, and allow proper caching with the same name.

Dynamic field in a Django model

I have a Django model that have some fields as following.
I would like to get advice regarding the pros and cons of adding dynamically some attributes to those models (based on some calculations that depend on real-time information from the user session).
I'm happy with the current implementation, but I'm somehow concerned about the implications in the long-term.
Is it considered a bad Design Pattern? In this case are there alternatives that permit to do that more cleanly?
Is it cleaner to make the calculations in template tags?
Maybe putting this logic in a manager? Proxy model?
I thought doing this as properties, but the calculations depend on the request object, so it has to be done in the view somehow.
Thanks a lot.
class Printer(TimeStampedModel):
user = models.ForeignKey(
User)
minimal_cost = models.IntegerField()
active = models.BooleanField(default=True)
def some_view(request):
for printer in printer_list:
printer.distance = printer.distance_point(user_point)
printer.materials_costs = MaterialsCosts(printer, cart=cart)
printer.minimum_price = printer.materials_costs.minimum_printer_price()
You can move your calculation method to models.py, you should not perform such calculations inside a view directly.
class Printer(TimeStampedModel):
user = models.ForeignKey(
User)
minimal_cost = models.IntegerField()
active = models.BooleanField(default=True)
#classmethod
def set_my_attrs(cls):
printer_list = cls.objects.all() #Im assuming that you need all the printers for your calculations
for printer in printer_list:
printer.distance = printer.distance_point(user_point)
printer.materials_costs = MaterialsCosts(printer, cart=cart)
printer.minimum_price = printer.materials_costs.minimum_printer_price()
printer.save() # dont forget saving :)
Then in your views.py
def some_view(request):
Printer.set_my_attrs()

Extending two related queries on same model in Django

Consider a Django model that forms a connection between two other model instances:
class User(models.Model):
pass
class Meeting(models.Model):
user_a = models.ForeignKey(User, related_name='a_meetings')
user_b = models.ForeignKey(User, related_name='b_meetings')
Now suppose you want to define a function that returns all of a Users meetings, regardless of if he is side a or b. The naive implementation would be:
class User(models.Model):
#property
def meetings(self):
return list(self.a_meetings.all()) + list(self.b_meetings.all())
What's the 'right' way to have this function return the matching QuerySet instead of a list? Bonus points for an elegant solution that doesn't use an ugly Q query ;)
I think Qs are inevitable here.
The main principle with these things is always to start from the model you actually want to get: so since you want meetings, start there.
return Meeting.objects.filter(Q(user_a=self) | Q(user_b=self))

Model "Behaviors" in Django

I'm new to Django, moved from PHP with Propel ORM engine.
So here is what I am currently doing in Django. My website has several models like
Book,
Publisher,
Comment,
Article and so on (it's not the point)
Each of them can can be
liked or disliked by a user (only once) changing the model's rating by +1 or -1.
In terms of PHP i would create a behavior, for ex. "Rateable" which would add some fields and methods to the original model and query class (like get_rating(), order_by_rating(), etc) and create a separate table for each model, for ex. book_rating_history which would hold all of the ratings for each object, to determine if the user can or can't change the rating (or show all object's ratings, if necessary). So all I would need to do is to specify the "Rateable" behavior in the model declaration, and that's all. Everything else is done automatically.
The question is - how to solve this in Django? How to model correctly? Which techniques do you use in similar cases?
You'll want to store ratings and books separately, something like this (untested).
from django.contrib.auth.models import User
from django.db import models
from django.db.models import Sum
class BookRating(models.Model):
user = models.ForeignKey(User)
book = models.ForeignKey('Book')
# you'll want to enforce that this is only ever +1 or -1
rating = models.IntegerField()
class Meta:
unique_together = (('user', 'book'),)
class Book(models.Model):
title = models.CharField(max_length = 50)
def rating(self):
return BookRating.objects.filter(book = self).aggregate(Sum('rating'))
unique_together enforces that each user can only rate a given book once.
You can then use this something like:
book = Book.objects.get(pk = 1)
rating = book.rating()
Add a comment if you have problems with this - I've not tested it, but hopefully this is enough to get you started.
You can probably avoid each object (books, publishers, comments, articles) having a separate rating object using content types, if you want.
Alternatively, you might consider looking at existing reusable apps that handle liking, like django-likes or phileo.
You can define special methods (for example vote, get_rating, etc.) only onсe in abstract model and then create your "Rateable" models using this one.
class Rateable(models.Model):
class Meta:
abstract = True
def vote(self, *args, **kwargs):
...
def rating(self, *args, **kwargs):
...
class Book(Rateable):
...
Also it is better to use single model for storing rating data witch content types as noticed Dominic Rodger

Django Managers

I have the following models code :
from django.db import models
from categories.models import Category
class MusicManager(models.Manager):
def get_query_set(self):
return super(MusicManager, self).get_query_set().filter(category='Music')
def count_music(self):
return self.all().count()
class SportManager(models.Manager):
def get_query_set(self):
return super(MusicManager, self).get_query_set().filter(category='Sport')
class Event(models.Model):
title = models.CharField(max_length=120)
category = models.ForeignKey(Category)
objects = models.Manager()
music = MusicManager()
sport = SportManager()
Now by registering MusicManager() and SportManager() I am able to call Event.music.all() and Event.sport.all() queries. But how can I create Event.music.count() ? Should I call self.all() in count_music() function of MusicManager to query only on elements with 'Music' category or do I still need to filter through them in search for category first ?
You can think of a manager as a 'starting point' for a query - you can continue to chain filters just as if you'd started out with the default manager.
For example, Event.objects.filter(category='Music').filter(title='Beatles Concert') is functionally equivalent to Event.music.filter(title='Beatles Concert')
So, as Daniel says, you don't really need to do anything special, just choose one of your custom managers instead of objects and go from there.
You don't need to do anything (and your count_music method is unnecessary). The count() method will use the existing query as defined by get_query_set.