Code to display Django (Reverse) related table throws error - python

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.

Related

Annotating a Subquery with queryset filtering methods from another model through Many to Many fields

I'm not sure how to make this possible, but I'm hoping to understand the intended method to do the following:
I have a simple model:
class Author(models.Model):
id = models.TextField(primary_key=True, default=uuid4)
name = models.TextField()
main_titles = models.ManyToManyField(
"Book",
through="BookMainAuthor",
related_name="main_authors",
)
objects = AuthorManager.from_queryset(AuthorQuerySet)()
class Book(models.Model):
id = models.TextField(primary_key=True, default=uuid4)
title = models.TextField()
genre = models.ForeignKey("Genre", on_delete=models.CASCADE)
objects = BookManager.from_queryset(BookQuerySet)()
class BookQuerySet(QuerySet):
def by_genre_scifi(self) -> QuerySet:
return self.filter(**self.LOOKUP_POPULAR_SCIFI)
I'd like to add a new QuerySet method for AuthorQuerySet to annotate Author objects using the method from BookQuerySet above. I've tried the following, which is incorrect:
class AuthorQuerySet(QuerySet):
def annotate_with_total_titles_by_genres(self) -> QuerySet:
main_titles_by_genre_sci_fi_query = Book.objects.filter(main_authors__in=[OuterRef('pk')]).all()
.by_genre_sci_fi()
.annotate(cnt=Count('*'))
.values('cnt')[:1]
return self.annotate(sci_fi_titles_total =
Subquery(main_titles_by_genre_sci_fi_query, output_field=IntegerField()))
Intended usage:
annotated_authors = Author.objects.filter(<some filter>).annotate_with_total_titles_by_genres()
There are additional fields in the lookup not shown in the model above, but the method here is working, and returns a BookQuerySet filtered by the lookup:
Book.objects.filter(main_authors__in=['some_author_id']).all().by_genre_sci_fi()
Similarly, I can run the subquery independently and get the count like so:
`Book.objects.filter(main_authors__in=['some_author_id']).all()
.by_genre_sci_fi()
.annotate(cnt=Count('*'))
.values('cnt')[:1]`
Out[1]: <BookQuerySet [{'cnt': 1}]>
However when I try to annotate using the AuthorQuerySet method above, I get None for every entry.
I wonder if there is an issue here with OuterRef and using in which will evaluate each character independently if it receives a string. If I try running it without the square parens:
ProgrammingError: syntax error at or near ""bookshop_author"" LINE 1: ...RE (U0."deleted_at" IS NULL AND U1."author_id" IN "bookshop_...

Django, accessing model methods

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())

django.db.utils.OperationalError; filtering QuerySet with date lookup

I'm going through an example where a Model has a DateTimeField defined upon it. When trying to create a filtered QuerySet with a date lookup, the following error is returned. I'm confused as to why this error is being raised. It makes it sound like a function that I wrote is causing the error (which is not the case), but perhaps I'm reading that incorrectly.
django.db.utils.OperationalError: user-defined function raised exception
>>> a = Employee.objects.create(
alias="User", age=0, hired=datetime(2020, 6, 12), experience=0
)
>>> result = Employee.objects.filter(hired__date__gt=datetime(2019, 1, 1))
from django.db import models
class Employee(models.Model):
alias = models.CharField(unique=True, max_length=11)
age = models.IntegerField()
hired = models.DateTimeField()
experience = models.IntegerField()
def __str__(self):
return self.alias
It should be,
result = Employee.objects.filter(hired__date__gte=datetime.date(2019, 1, 1))

csv import to manytomanyfield Django

I used below code to import csv file to django model containing manytomanyfield Release.metamodules
>>> from app.models import Metamodule,Release
>>> reldata = csv.reader(open('/root/Django-1.6.5/django/bin/dashboard/release.csv'),delimiter=',')
for row in reldata:
q = Release(number = row[0],
notes= row[1],
changes = row[2],
metamodules = Metamodule.objects.filter(name = row[3]))
try:
q.save()
except:
# if the're a problem anywhere, you wanna know about it
print "there was a problem with line"
Error:
Traceback (most recent call last):
File "<console>", line 5, in <module>
File "/usr/local/lib/python2.7/site-packages/django/db/models/base.py", line 416, in __init__
raise TypeError("'%s' is an invalid keyword argument for this function" % list(kwargs)[0])
TypeError: 'metamodules' is an invalid keyword argument for this function
As the field is ManyToManyField i used objects.fileter to get multiple records. But it is returning error.Please help me to fix this issue
models.py:
class Metamodule(models.Model):
name = models.CharField(max_length=50)
version = models.IntegerField(default=0)
modulename = models.ForeignKey(Module)
createdate = models.DateField(auto_now=True, null=True)
createdby = models.CharField(max_length=50)
def __unicode__(self):
return unicode(self.name)
class Release(models.Model):
number = models.IntegerField(default=0)
notes = models.CharField(max_length=50)
changes = models.CharField(max_length=50)
metamodules = models.ManyToManyField(Metamodule)
def __unicode__(self):
return unicode(self.number)
You can't create your Release object like that. You cannot create m2m relations from unsaved objects. See here
Try something like this :
for row in reldata:
q = Release(number=row[0], notes=row[1], changes=row[2])
# You have to save the object before adding the m2m relations
q.save()
metamodules = Metamodule.objects.filter(name=row[3])
for metamodule in metamodules:
q.metamodules.add(metamodule)
There is probably a better way to do the for loop but this is what you want to achieve.

Updating Django model using selected collection of objects

I am learning Django and I refer djangobook.com.
I have a model like this:
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
def __unicode__(self):
return self.name
I have populated some data (as described in the demo) and tried to update the value of a particular field of a record (as explained) and this works perfectly fine:
>>> publisher_list = Publisher.objects.all()
>>> p = publisher_list[0]
>>> p.name = u'Apress'
>>> p.save()
But when I try the below (which I assume is equivalent to the above), it does not work. The name is not updated in the database. What am I doing wrong here?
>>> publisher_list = Publisher.objects.all()
>>> publisher_list[0].name = 'Apress'
>>> publisher_list[0].save()
Reference: http://djangobook.com/en/2.0/chapter05/
Thanks.
Your indexing is retrieving the model from the database multiple times. Bind the model to a name before mutating and saving it.
According to QuerySet.__getitem__(), retrieving by item, for example by qs[0], is not cached and will hit DB for each accessing, unless the queryset has been evaluated and thus cache has been filled:
>>> qs = User.objects.all()[:10]
>>> qs[0] is qs[0] # Similar with your issue, you modified the attribute of the first and tried to save the latter.
False
>>> len(qs) # evaluate and fill cache
10
>>> qs[0] is qs[0]
True
So you could
follow Ignacio's answer, or just p = Publisher.objects.all()[0]
or, evaluate the queryset before indexing. Whether this could be easier depends on your code logic.

Categories