Django, accessing model methods - python

In the Django documentation, they recommend writing business logic in Model.
How do the View layer or queryset access the methods in Model ?
As per example in documentation (https://docs.djangoproject.com/en/3.0/topics/db/models/)
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birth_date = models.DateField()
def baby_boomer_status(self):
"Returns the person's baby-boomer status."
import datetime
if self.birth_date < datetime.date(1945, 8, 1):
return "Pre-boomer"
elif self.birth_date < datetime.date(1965, 1, 1):
return "Baby boomer"
else:
return "Post-boomer"
How do view layer access the baby_boomer_status ?
I have a little experienced in Django development but I used to write logics in View itself.

This can be done by simply calling function. For example,
>>> from .models import Person
>>> person = Person.objects.get(id=1) # Remember getting the person object
>>> person.baby_boomer_status()
You have to first get person object otherwise, it will return function itself, e.g
>>> from .models import Person
>>> person.baby_boomer_status()
>>> <function AppName.models.Person.baby_boomer_status(self)>

You can just call the method on the person instance:
person = Person.objects.get(id=1)
print(person.baby_boomer_status())

You can iterate over QuerySet and call the model method as
for person in Person.objects.all():
print(person.baby_boomer_status())
If you have a single object, just call the method directly as,
print(Person.objects.get(pk=123).baby_boomer_status())

Related

Code to display Django (Reverse) related table throws error

I'm quite new to Django and practicing Models section of Django by following its official tutorial. I also created a project of my own and try to apply similar concepts.
This is my models.py;
from django.db import models
class Experience(models. Model):
o01_position = models.CharField(max_length=50)
o02_year_in = models.DateField(null=True)
o03_year_out = models.DateField(null=True)
o04_project = models.CharField(max_length=100)
o05_company = models.CharField(max_length=50)
o06_location = models.CharField(max_length=50)
def __str__(self):
return self.o01_position}
class Prjdesc(models.Model):
o00_key = models.ForeignKey(
Experience, on_delete=models.CASCADE)
o07_p_desc = models.CharField(max_length=250)
def __str__(self):
return self.o07_p_desc
class Jobdesc(models.Model):
o00_key = models.ForeignKey(Experience, on_delete=models.CASCADE)
o08_job_desc = models.CharField(max_length=250)
def __str__(self):
return self.o08_job_desc
Now when I run below command in Python/Django shell it runs as expected with the related data.
>>> x = Experience.objects.get( pk = 2 )
>>> x
<Experience: Head Office Technical Office Manager>
Below two also work as expected:
>>> y = Prjdesc.objects.get( pk = 11 )
>>> y
<Prjdesc: Description 1>
>>> x.prjdesc_set.all()
<QuerySet [<Prjdesc: Description 1>, <Prjdesc: Description 2>]>
However this expression does not return anything although it should return its related record in Experience Class.
>>> y.experience
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'Prjdesc' object has no attribute 'experience'
Could you tell me what I am missing here?
As you mentioned in one of the comments above:
Strangely it returns this; Traceback (most recent call last): File "", line 1, in AttributeError: 'Prjdesc' object has no attribute 'experience'.
You simply need to write c.o00_key not c.experience, you confused with official docs, they give their field name also as experince.
Generally, ForeignKey is created using model name in smallcase while defining field, and the related_name sets to model name as prefix and _set as suffix by default, so it will be prjdesc_set in your case or you can override it by using ForeignKey.related_name in the field.
With your current models use this:
>>> x = Experience.objects.get(pk=2)
>>> x
<Experience: Head Office Technical Office Manager>
>>> c = x.prjdesc_set.create(o07_p_desc='Description 5')
>>> c
<Prjdesc: Description 5>
>>> c.o00_key
>>> c
<Experience: Head Office Technical Office manager>
Note: Also it's better to use f stings in the __str__() method, so in your models.py:
class Experience(models.Model):
...
...
def __str__(self):
return f"{self.o01_position}"
class Prjdesc(models.Model):
...
...
def __str__(self):
return f"{self.o07_p_desc}"
class Jobdesc(models.Model):
...
...
def __str__(self):
return f"{self.o08_job_desc}"
If you pay attention:
c = q.choice_set.create(choice_text='Just hacking again', votes=0)
there is a call through _set. But not at all:
q = Question.objects.get(pk=1)
followed by:
q.choice# which will throw the same error
By using the primary model, you can get the data associated with it from the secondary model. To do this, a special property (object) with the name secondary model_set is created in the primary model by default. In your case, for example:
x = Experience.objects.get(pk=1)
x.prjdesc_set.all()
That is, we get all the values of the secondary model with pk=1 of the primary one (one-to-many access).
If you need to get the value from the primary model from the secondary model, then as already mentioned:
Prjdesc.objects.get(id=1).o00_key.o01_position
In this case, get is used, that is, the value should be one, if more is expected, then filter should be applied.

Django: How to use an annotation from a parent queryset?

I have an Item class which can be annotated using a custom queryset add_is_favorite_for method:
class ItemQuerySet(QuerySet):
def add_is_favorite_for(self, user):
"""add a boolean to know if the item is favorited by the given user"""
condition = Q(id__in=Item.objects.filter(favoriters=user).values("id"))
return self.annotate(is_favorite=Condition(condition)) # True or False
class Item(Model):
objects = Manager.from_queryset(ItemQuerySet)()
It works as expected. For example:
>>> user = User.objects.get(id=1)
>>> Item.objects.add_is_favorite_for(user) # each item has now a `is_favorite` field
Then, I added a Factory model and link Item model to it using a 1->N relationship:
class Factory(Model):
pass # ...
class Item(Model):
objects = Manager.from_queryset(ItemQuerySet)()
advised_in = models.ForeignKey(
Factory,
on_delete=models.CASCADE,
related_name="advised_items",
)
Now, I'd like to be able to return a Factory QuerySet, whose advised_items fields will all contain the is_favorite annotation too.
I don't know how to do this, I saw no example of such a thing in the doc, maybe I missed it.
You can work with a Prefetch object [Django-doc]:
from django.db.models import Prefetch
Factory.objects.prefetch_related(
Prefetch('advised_items', queryset=Item.objects.add_is_favorite_for(some_user))
)

How to fix error "ValueError: <Follower: Follower object (None)> instance is not saved." Use bulk = False or save the object first. "

I wanted to create a follower system, and I get a "ValueError: instance is not saved." Use bulk = False or save the object first. " .Please how to solve this problem, sorry for my bad writing!!
#my models.py
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
class Follower(models.Model):
follower = models.ForeignKey(User, related_name='following', on_delete=None)
following = models.ForeignKey(User, related_name='followers', on_delete=None)
class Meta:
unique_together = ('follower', 'following')
def __unicode__(self):
return u'%s follows %s' % (self.follower, self.following)
#in the interpreter
>>> from followers.models import *
>>> john = User.objects.create_user('john', 'lennon#thebeatles.com', 'password')
>>> paul = User.objects.create_user('paul', 'mccartney#thebeatles.com', 'password')
>>> george = User.objects.create_user('george', 'harrison#thebeatles.com', 'password')
>>> ringo = User.objects.create_user('ringo', 'starr#thebeatles.com', 'password')
#the mistake comes from here
>>> john.following.add(Follower(following=paul))
and I get
in add "the object first." % obj
ValueError: instance isn't saved. Use bulk=False or save the object first.`
You should create a Follower object like:
Follower.objects.create(follower=john, following=paul)
You can not add unsaved objects to a one-to-many or many-to-many relation, since at that moment they do not have a primary key yet. Since here your ForeignKeys are non-nullable, you can not first save the Follower object with one of the two relations filled in, and then use .add(..) to add the other end, but that would be more expensive anyway, since it results in two queries.
You can also make Follower objects in bulk like:
Follower.objects.bulk_create([
Follower(follower=john, following=paul),
Follower(follower=john, following=ringo),
Follower(follower=ringo, following=paul),
Follower(follower=george, following=ringo)
])

When are model methods called in django

I am trying to understand how model methods work.
Taking the following example: source here
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birth_date = models.DateField()
def baby_boomer_status(self):
"Returns the person's baby-boomer status."
import datetime
if self.birth_date < datetime.date(1945, 8, 1):
return "Pre-boomer"
elif self.birth_date < datetime.date(1965, 1, 1):
return "Baby boomer"
else:
return "Post-boomer"
When is baby_boomer_status method called?
I tried replacing return with print but the method was never called.
How does this work? Sorry for the noob question.
If you have a Person object
my_person = Person.objects.first()
You can call the model method:
my_person.baby_boomer_status()
Since this return a string, you can print its result
print my_person.baby_boomer_status()

Specialized django query

As you can guess from the title, I'm not exactly sure how to describe what I want. Please take a look at the following classes:
from django.db import models
from django.contrib.auth.models import User as Auth_User
class User(Auth_User):
Portfolio = models.ManyToManyField('PortfolioItem', through='SkillTag')
Age = models.IntegerField(blank=False)
#property
def full_name(self):
return self.first_name + ' ' + self.last_name
def __unicode__(self):
return self.full_name
class PortfolioItem(models.Model):
Title = models.CharField(max_length=200, blank=False)
class SkillTag(models.Model):
User = models.ForeignKey('User')
PortfolioItem = models.ForeignKey('PortfolioItem')
Tag_Name = models.CharField(max_length=200, blank=False)
What I need to do, is for every user, get all the Tag_Name values of it's SkillTags, how do I do this?
You can do something like this
class User(Auth_User):
#other attributes
def tag_names(self):
return self.skilltag_set.values_list('Tag_Name', flat=True)
So, here, we are doing a couple of things:
Querying in reverse ForeignKey relationship.
Since you are not using a related_name in the ForeignKey attribute, by default django would assign the model name (lowercase) followed by _set attribute, which makes it .skilltag_set.all()
values_list
Returns a ValuesQuerySet — a QuerySet subclass that returns tuples when used as an iterable, rather than model-instance objects.
Example: [('a'), ('b'), ('c')]
Basically, you are retriving an iterable of ValuesQuerySet (think of it as a list or any other iterables) consisting of tuples.
flat=True
This basically flattens the on-tuples into single values.
Example: ['a', 'b', 'c']
most obvious: using the reverse relationship of ForeignKey fields:
def skill_names_1(user):
return [t.name for t in user.skilltag_set.all()]
The same thing, but explicitly selecting for the user. also, it fetches only the required field from the database.
def skill_names_2(user):
return SkillTag.objects.filter(User=user).values_list('Tag_Name',flat=True)
Either of these can also work as a method of User. Of course, typically the argument would be called self instead of user.
All the skills for a group of users:
def skill_names_3(users):
return SkillTag.objects.filter(User__in=users).values_list('Tag_Name',flat=True)

Categories