Updating Django model using selected collection of objects - python

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.

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.

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_...

Function to copy fields from a model to another model in django

I want to merge 2 different models with different but overlapping fields.
I try to write a function that copy the fields with its data from model A to model B.
def create_field():
old = DetailItem.objects.all()
new = CrawlItem.objects.all()
for item in old:
c = CrawlItem.objects.get(title=item.title, description=item.description, link=item.link, cpvcode=item.cpvcode, postalcode=item.postalcode )
c.save()
and i don't know whereis the mistake. I want to have a model that contains the data from the old model and some new fields.
Here is my code for the two models:
class DetailItem(Base):
title = models.CharField(max_length=500)
description = models.CharField(max_length=20000)
link = models.URLField()
cpvcode = models.ManyToManyField('CPVCode',related_name='cpv')
postalcode = models.ForeignKey('PostalCode',on_delete=models.SET_NULL,null=True,blank=True,related_name='postal')
def __str__(self):
return self.title
class CrawlItem(Base):
guid = models.CharField( primary_key=True, max_length=500)
title = models.CharField(max_length=500)
link = models.URLField()
description = models.CharField(max_length=2000)
pubdate = models.DateTimeField()
detail = models.ForeignKey('DetailItem',on_delete=models.SET_NULL,null=True,blank=True,related_name='crawldetail')
def __str__(self):
return str(self.title)
This is what I want to get:
class CrawlItem(Base):
guid = ...
title = ...
link = ...
cpvcodes = ...
postalcode = ...
pubdate = ...
vergabestelle = ...
vergabeart = ...
anmeldefrist = ...
description = ...
Any ideas how to get there are highly appreciated!
It's not entirely clear which objects already exist in your database, and when you consider two objects to be "equal". Assuming a CrawlItem is "equal" to a "DetailItem" when title, description and link are the same, then you can use the update_or_create function like this:
for item in old:
CrawlItem.objects.update_or_create(
# if matching, existing item updated, otherwise new item created
title=item.title, description=item.description, link=item.link,
defaults = {'cpvcode': item.cpvcode, 'postalcode': item.postalcode}
)
Alternatively, if the two models are linked with the fk as shown in your models (and you want to remove it later on), then you don't even need to check for "equal" objects because you already have all the related ones (assuming title, description and link are already equal):
for item in old:
item.crawldetail.all().update(cpvcode=item.cpvcode, postalcode=item.postalcode)
in your for statement you are just trying to select de CrawlItem with the same values as DetailItem using the get queryset method.
if you want to create a CrawlItem you should use the create method (docs here -> https://docs.djangoproject.com/en/2.2/ref/models/querysets/#create)
c = CrawlItem.objects.create(title=item.title, ..., postalcode=item.postalcode)
it will be created when create() is called, so, you don't need to save it afterwards, c is set to the newly created object.
For performance reasons you can use bulk_create() method as follows (docs here -> https://docs.djangoproject.com/en/2.2/ref/models/querysets/#bulk-create)
new_crawlitems = []
for item in old:
new_crawlitems.append(CrawlItem(title=item.title, description=item.description, link=item.link, cpvcode=item.cpvcode, postalcode=item.postalcode)
CrawlItem.objects.bulk_create(new_crawlitems)
Hope this helps and put you on the right direction.
G.

How to get properties of a model attribute?

Let's say that I have a class such as :
class MyClass(models.Model):
attributeA = models.CharField(max_length=100)
attributeB = models.IntegerField()
attributeC = models.CharField(max_length = 150, blank=True, nullable = True)
attributeD = models.ForeignKey('ModelB',related_name='FK_modelB')
attributeE = models.ManyToManyField('ModelC')
What I want to do is to get the properties of every attribute, not just the name that I got with :
my_instance._meta.get_all_field_name()
(which gave me a list of attributes names). No, what I want is, for every attribute, know what is his type (CharField,IntegerField, ForeignKey, ManyToManyField...), who's related if it's a ForeignKey / ManyToManyField and all the meta data such as max_length and so on.
The aim of it is to serialize a class into a XML and the representation in the XML will be different if it's a ManyToManyField, a ForeignKey or a simple value.
By the way, If anyone know a great class serializer to XML, it would help me a lot !
Thanks for your responses !
Django models _meta.fields is fields list that you can access to get field attributes:
>>> from django.contrib.auth.models import User
>>> u = User.objects.all()[0]
>>> u._meta.fields[1].__class__.__name__
'CharField'
>>> u._meta.fields[1].name
'username'
>>> u._meta.fields[1].max_length
30
>>> u._meta.fields[1].blank
False
# ...
You can get attributes of a specific field by using get_field()
MyClass._meta.get_field('attributeA').max_length

How to move stored procedure to django model class and use them in filter/exclude?

How to move stored procedure to django model class and use them in filter/exclude?
As said here What is the best way to access stored procedures in Django's ORM it should be possible.
In another word, how can i accomplish something like this:
class Project(models.Model):
name = models.CharField()
def is_finished(self):
count = self.task_set.all().count()
count2 = self.task_set.filter(finished=True).count()
if count == count2:
return True
else:
return False
class Task(models.Model):
name = models.CharField()
finished = models.BooleanField()
project = models.ForeignKey(Project)
#somewhere else in the code...
finished_projects = Project.objects.filter(is_finished=True)
Not sure why you are referring to stored procedures in this context.
But if i understand your example correct, your problem is that you can filter only by modelfields that have a corresponding field in a database table.
And therefore you can't use django's orm to filter by methods and properties.
But you can achieve what you want using a list comprehension:
finished_projects = [p for p in Project.objects.all() if p.is_finished()]
One solution is denormalization:
class Project(models.Model):
name = models.CharField()
is_finished = models.BooleanField()
def _is_finished(self):
return self.task_set.exclude(finished=True).exists()
def update_finished(self):
self.is_finished = self._is_finished()
self.save()
class Task(models.Model):
name = models.CharField()
finished = models.BooleanField()
project = models.ForeignKey(Project)
def save(*args, **kwargs):
res = super(Task, self).save(*args, **kwargs)
self.project.update_finished()
return res
#somewhere else in the code...
finished_projects = Project.objects.filter(is_finished=True)
It is nice if you have much more reads than writes because reads will be very fast (faster than e.g. using stored procedures). But you should take care of consistency yourselves.
Django's aggregates or 'raw' support can be often used to implement stored procedure logic.

Categories