I have a class B that contains a ForeignKey relation to class A. When I instantiate B I can access field 'a' but the reverse relation (which should be created automatically) raises an error. Concretely, I have the following class definitions:
from django.db.models import Model, CharField, DateField, ForeignKey
from django.urls import reverse
class Patient(Model):
GENDER = (
('M', 'Male'),
('F', 'Female'),
('U', 'Unknown'),
)
last_name = CharField(max_length=128, null=False)
first_name = CharField(max_length=128, null=False, default='')
gender = CharField(max_length=1, choices=GENDER, null=False)
dob = DateField(null=False)
def get_absolute_url(self):
return reverse('patient_detail', args=[str(self.id)])
def __str__(self):
return '{}, {} ({}, {})'.format(self.last_name, self.first_name, self.gender, self.dob)
class AttributeSet(Model):
name = CharField(max_length=128, null=False)
description = CharField(max_length=256, blank=True, default='')
def get_absolute_url(self):
return reverse('attribute_set_detail', args=[str(self.id)])
def __str__(self):
return self.name
class AttributeSetInstance(Model):
patient = ForeignKey('Patient', null=False) # Automatic 'attribute_set_instance_set' backward relation?
attribute_set = ForeignKey('AttributeSet', null=False)
def get_absolute_url(self):
return reverse('attribute_set_instance_detail', args=[str(self.id)])
def __str__(self):
return self.attribute_set.name
When I try to create a new AttributeSetInstance with a Patient and AttributeSet argument I can access the patient and attribute_set fields, but not vice versa. Like so:
Python 2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 12:39:47)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from app.models import Patient, AttributeSet, AttributeSetInstance
>>> p = Patient(last_name='Doe', first_name='John', gender='M', dob='1973-07-16')
>>> p
<Patient: Doe, John (M, 1973-07-16)>
>>> a = AttributeSet(name='Set1')
>>> a
<AttributeSet: Set1>
>>> i = AttributeSetInstance(patient=p, attribute_set=a)
>>> i
<AttributeSetInstance: Set1>
>>> i.patient
<Patient: Doe, John (M, 1973-07-16)>
>>> i.attribute_set
<AttributeSet: Set1>
>>> p.attribute_set_instance_set
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'Patient' object has no attribute 'attribute_set_instance_set'
>>>
So, i.patient works, but p.attribute_set_instance_set does not. As far as I could figure out, the default manager that is assigned to ForeignKey relationships appends a '_set' postfix to the backward relation.
Any idea what I'm doing wrong? It would be of great help.
It's probably something silly...
Ralph
You've added underscores where there shouldn't be any. The default related name is the lower case name of the model plus _set, so for AttributeSetInstance it is attributesetinstance_set.
If you want to use your underscore version, you can set it as the explicit related_name argument to the ForeignKey field.
Related
I am using the django shell to look at some querysets. I am able to retrieve all of my Student objects with .all() but if I then try to retrieve one object with .get() or filter on a field, I'm getting empty returns. The project seems to run ok on the browser (I'm still in the progress of writing all my tests but functionally it is working). I am able to successfully use get() or filter on a field with my Objectivemodel.
Here is what I get with all():
>>> from gradebook.models import Student, Objective
>>> s = Student.objects.all()
>>> for i in s:
... print(i.student_first)
...
John
Sally
Keith
Terrance
Henry
Analise
Ron
>>>
Now if I try to use get():
>>> qs = Student.objects.get(student_first='Analise')
Traceback (most recent call last):
gradebook.models.Student.DoesNotExist: Student matching query does not exist.
Or filter on a field:
>>> s.filter(student_first='Ron')
<QuerySet []>
>>>
Student model
class Student(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
student_first = models.CharField(max_length=30)
student_last = models.CharField(max_length=30)
nickname = models.CharField(max_length=31)
fullname = models.CharField(max_length=60)
attend = models.BooleanField(default=True)
student_number = models.IntegerField()
email = models.EmailField(max_length=50)
def __str__(self):
return self.fullname
My application uses this rather simple model, to access data from an existing table:
class Table01(models.Model):
id = models.IntegerField(primary_key=True)
fecha = models.DateField(blank=True, null=True)
sucursal = models.IntegerField(blank=True, null=True)
remito = models.IntegerField(blank=True, null=True)
codcli = models.IntegerField(blank=True, null=True)
razon = models.CharField(max_length=25, blank=True, null=True)
domi = models.CharField(max_length=25, blank=True, null=True)
turno = models.IntegerField(blank=True, null=True)
codart = models.TextField(max_length=6, blank=True, null=True)
canti = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True)
precio = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True)
tot = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True)
class Meta:
managed = False
db_table = "TABLE01"
app_label = "datatables"
The backend for this database is SQLite
With Django 2.1.7 and this model, my application was performing successfully the following
query:
sumrem = (Table01.objects.filter(fecha = some_date, turno = some_turno, codcli =some_customer).values("fecha", "turno", "sucursal", "remito", "razon")
.annotate(Sum(F("tot"), distinct=True)))
to get the distinct sum of the 'tot' field, which was working as expected on Django 2.1.7
When I upgraded to Django 2.2.5, this error appeared:
Python 3.7.3 (default, Apr 3 2019, 05:39:12)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from .models import Table01
>>> from django.db.models import F, Sum, Count
>>> sumrem = Table01.objects.filter(fecha='2019-05-10', turno=4, codcli=50).values("fecha", "turno", "sucursal", "remito", "razon").annotate(Sum(F("tot"), distinct=True))
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/ricardo/desarrollo/estacion/vr1/lib/python3.7/site-packages/django/db/models/aggregates.py", line 26, in __init__
raise TypeError("%s does not allow distinct." % self.__class__.__name__)
TypeError: Sum does not allow distinct.
I did a little research, and found that from version 2.2, according to the changelog, "...The handling of DISTINCT aggregation is added to the Aggregate class. Adding allow_distinct=True as a class attribute on Aggregate subclasses allows a distinct keyword argument to be specified on initialization to ensure that the aggregate function is only called for each distinct value of expression"
So, I created this little piece of code, following the example on the reference of Aggregate() to implement the Sum function, and set the allow_distinct class attribute, to be able to pass distinct=True in the query.
#models.py
from django.db.models import Aggregate
class SumD(Aggregate):
function = "SUM"
allow_distinct = True
class CountD(Aggregate):
function = "COUNT"
allow_distinct = True
When I run the query now, this error happens:
>>> from .models import Table01, SumD, CountD
>>> from django.db.models import F, Sum, Count
>>> sumrem = Table01.objects.filter(fecha='2019-05-10', turno=4, codcli=50).values("fecha", "turno", "sucursal", "remito", "razon").annotate(SumD(F("tot"), distinct=True))
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/ricardo/desarrollo/estacion/vr1/lib/python3.7/site-packages/django/db/models/query.py", line 1032, in annotate
if arg.default_alias in kwargs:
File "/home/ricardo/desarrollo/estacion/vr1/lib/python3.7/site-packages/django/db/models/aggregates.py", line 64, in default_alias
return '%s__%s' % (expressions[0].name, self.name.lower())
AttributeError: 'NoneType' object has no attribute 'lower'
And now is when I'm really lost :) any help is really appreciated.
Sincerely, Ricardo.
Found it! Apparently, the Aggregate() class needs a name assigned to the result, unlike the 2.1 version where you didn't need the name, and the default name would be
field__sum
This code works:
sumrem = Table01.objects
.values('fecha', 'turno', 'sucursal', 'remito', 'razon')
.filter(fecha=some_date, turno=some_turno, codcli=some_customer)
.annotate(tot=SumD(F('tot'), distinct=True))
I believe this should be documented better. Best regards. Ricardo.
I have function with signal:
#receiver(post_save, sender=Task)
def my_handler():
executor = User.objects.filter(user_type='Executer')
executor.balance += Task.money
executor.save()
My function should add money to executor after task added. But it gives mistake like:
Internal Server Error: /api/v1/tasks/
Traceback (most recent call last):
...
File "/home/k/pro/freelance-django/free/lib/python3.5/site-packages/django/db/models/fields/__init__.py", line 1853, in get_prep_value
return int(value)
ValueError: invalid literal for int() with base 10: 'Executer'
[20/Sep/2017 14:00:30] "POST /api/v1/tasks/ HTTP/1.1" 500 188088
User class looks like:
from __future__ import unicode_literals
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
CUSTOMER = 1
EXECUTER = 2
USER_TYPES = (
(CUSTOMER, 'Customer'),
(EXECUTER, 'Executer'),
)
user_type = models.IntegerField(choices=USER_TYPES, default=EXECUTER, verbose_name='Тип пользователя')
balance = models.DecimalField(decimal_places=2, max_digits=7, default=0, verbose_name='Баланс')
def __str__(self):
return self.username
Task class from task.models is:
class Task(models.Model):
title = models.CharField(max_length=255, verbose_name='Заголовок')
description = models.CharField(max_length=255, verbose_name='Описание')
money = models.DecimalField(max_digits=7, decimal_places=2, default=0, verbose_name='Цена')
assignee = models.ForeignKey('users.User', related_name='assignee', null=True, verbose_name='Исполнитель')
created_by = models.ForeignKey('users.User', related_name='created_by', verbose_name='Кем был создан')
How should I make it work?
U don't have one object but objects so you need literal on them or do update(). The best option is use F expression which has a good performance because its executed on DB:
from django.db.models import F
#receiver(post_save, sender=Task)
def my_handler(sender, instance, **kwargs):
User.objects.filter(user_type=User.EXECUTER).update(balance=F('balance') + instance.money)
This update all records where user_type is Executer and update them balance in one query.
Django docs update
F expressions
Your user_type field is IntegerField and in your choices you have string value so u need change it to something like this:
class User(AbstractUser):
CUSTOMER = 1
EXECUTER = 2
USER_TYPES = (
(CUSTOMER, 'Customer'),
(EXECUTER, 'Executer'),
)
user_type = models.IntegerField(choices=USER_TYPES, default=EXECUTER, verbose_name='Тип пользователя')
balance = models.DecimalField(decimal_places=2, max_digits=7, default=0, verbose_name='Баланс')
In Django's many-to-many relationships extra fields, we can add extra fields, as the code below (from https://docs.djangoproject.com/en/dev/topics/db/models/#intermediary-manytomany), my question is how could I get the extra field easily, is it possible to not query on the intermediary table directly? the exactly example is:
>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(person=ringo, group=beatles,
... date_joined=date(1962, 8, 16),
... invite_reason="Needed a new drummer.")
>>> m1.save()
>>> beatles.members.all()
[<Person: Ringo Starr>]
>>> p = beatles.members.all()[0]
>>> p
[<Person: Ringo Starr>]
I want to get the date_joined value of p, I know it can be done by
>>> Membership.objects.get(person=p, group=beatles).date_joined
datetime.date(1962, 8, 16)
but is it possible to get it simply just like:
>>> p.xxx(*args)
=============== models.py ================
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self): # __unicode__ on Python 2
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self): # __unicode__ on Python 2
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
From the documentation page you link to:
Another way to access the same information is by querying the many-to-many reverse relationship from a Person object:
>>> ringos_membership = ringo.membership_set.get(group=beatles)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'
I am using the following models for my first django site. But I am currently having problems with how to access the wishes of a user.
class Group(models.Model):
title = models.CharField(max_length=100)
users = models.ManyToManyField(User, related_name='group_users')
description = models.TextField()
added = models.DateTimeField()
owner = models.ForeignKey(User)
def __unicode__(self):
return self.title
class Wish(models.Model):
name = models.CharField(max_length=50)
user = models.ForeignKey(User, related_name='wish_user')
bought = models.IntegerField(default=0)
bought_by = models.ForeignKey(User, related_name='wish_buyer')
added_by = models.ForeignKey(User, related_name='wish_adder')
cost = models.FloatField()
added = models.DateTimeField()
def __unicode__(self):
return self.name
def is_bought(self):
return self.bought % 2 == 1
def is_editable(self):
return self.added >= timezone.now() - datetime.timedelta(hours=1)
When I go to the django shell I get the following:
$ ./manage.py shell
>>> from django.contrib.auth.models import User
>>> from wish.models import Wish, Group
>>> user1 = User.objects.filter(id=1)[0]
>>> user1.group_set.all()
[]
>>> user1.wish_set.all()
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'User' object has no attribute 'wish_set'
>>>
Why doesn't the User object get the wish_set like it does get the group_set ?
That's because you renamed them to wish_user, wish_buyer and wish_adder. Whereas for the group you have wish_set implicity from the owner property and the explicit group_users.
The related_name parameter tells Django how to name the reverse relation. If it's not given it will be <field name>_set