How to display data from parent model using foreign key django - python

I want to display the child model data with the parent model data as well in a queryset.
This is my models in model.py
class Shareholders(models.Model):
sharehold_IC = models.CharField(primary_key=True, unique=True, validators=[RegexValidator(r'^\d{12,12}$'), only_int], max_length=12)
sharehold_name = models.CharField(max_length=100, null=True)
sharehold_email = models.CharField(max_length=50, null=True, unique=True)
sharehold_address = models.TextField(null=True, blank=True)
password = models.CharField(max_length=100)
def __str__(self):
return self.sharehold_name
class Meeting(models.Model):
MEETING_STATUS = (
('Coming Soon', 'Coming Soon'),
('Live', 'Live'),
('Closed', 'Closed')
)
meeting_ID = models.CharField(primary_key=True, max_length=6, validators=[RegexValidator(r'^\d{6,6}$')])
meeting_title = models.CharField(max_length=400, null=True)
meeting_date = models.DateField()
meeting_time = models.TimeField()
meeting_desc = models.CharField(max_length=500, null=True)
meeting_status = models.CharField(max_length=200, null=True, choices=MEETING_STATUS)
date_created = models.DateTimeField(default=timezone.now, null=True)
def __str__(self):
return self.meeting_ID
class Question(models.Model):
question_ID = models.AutoField(primary_key=True)
question = models.CharField(max_length=400, null=True)
meeting_id = models.ForeignKey(Meeting, on_delete=CASCADE)
shareholder_IC = models.ForeignKey(Shareholders_Meeting, on_delete=CASCADE, related_name='shareholder_ic')
date_created = models.DateTimeField(default=timezone.now, null=True)
def __str__(self):
return str(self.meeting_id)
I try to display all the data from the Question model with the shareholders details such as sharehold_name from the Shareholders model.
This is how I try to do in Views.py.
Views.py
def getMessages(response, meeting_id):
meeting = Meeting.objects.get(meeting_ID=meeting_id)
questions = Question.objects.filter(meeting_id=meeting.meeting_ID)
# print(list(questions.values('shareholder_IC_id')))
for i in questions:
print(i.shareholder_ic.all())
return JsonResponse({"questions":list(questions.values())})
But somehow I got this error AttributeError: 'Question' object has no attribute 'shareholder_ic'.
I want to get the result like this:
{'question_ID': 141, 'question': "I'm good. How are you?", 'meeting_id_id': '731404', 'shareholder_IC_id': '122311221231', 'date_created': datetime.datetime(2021, 10, 7, 3, 40, 12, 160029, tzinfo=<UTC>), 'sharehold_name':'John Steve', 'sharehold_email':'john#gmail.com'}
How do I fix this or is there any other way to display the data?
Thanks in advance

To begin with, you're not using the related_name attribute in your Question model correctly. It's not to use an alternative name to refer to the related model, the opposite, it's the name you want to use to refer to the Question model from the related model, Shareholders in your case. You have to remake your model:
class Question(models.Model):
question_ID = models.AutoField(primary_key=True)
question = models.CharField(max_length=400, null=True)
meeting_id = models.ForeignKey(Meeting, on_delete=CASCADE)
shareholder_IC = models.ForeignKey(Shareholders_Meeting, on_delete=CASCADE, related_name='questions')
date_created = models.DateTimeField(default=timezone.now, null=True)
def __str__(self):
return str(self.meeting_id)
Then, in your views.py you need to use the correct attribute name in the Question model, which is shareholders_IC:
def getMessages(request, meeting_id): # <- Shouldn't the first param be request?
meeting = Meeting.objects.get(meeting_ID=meeting_id)
# No need to use meeting.meeting_ID here, you can use the meeting instance
questions = Question.objects.filter(meeting_id=meeting)
for i in questions:
print(i.shareholder_IC) # <- No .all(), a question has only one shareholder
return JsonResponse({"questions":list(questions.values())})
However, to achieve what you want, you should either use Django REST Framework, or serialize yourself the different instances that you'll get:
def getMessages(request, meeting_id):
meeting = Meeting.objects.get(meeting_ID=meeting_id)
questions = Question.objects.filter(meeting_id=meeting)
questions_list = []
for i in questions:
questions_list.append(
{
"question_ID": i.question_ID,
"question": i.question,
"shareholder_IC_id": i.shareholder_IC.shareholder_IC,
...
}
)
return JsonResponse(questions_list)
I would recommend two main things:
Learn about relationships in Django and how to represent them in your models. You shouldn't have an attribute meeting_id, for example, in your Question. Although internally the id is indeed being used to query the Meeting, the attribute has a relationship to the model, not to the ID.
Use DRF. It will really help you a lot.

You must use backward relations
questions = Question.shareholder_ic.filter(meeting_id=meeting.meeting_ID)
Do not touch it on the object
For other settings, create the required query set in manager

Related

How to assign default value of the model based on the value of ForeignKey

I have the Account model were I store information about preferred units.
However I also want to allow user to change the units for particular exercise which by default should be Account.units.
Here are my models:
class Account(models.Model):
"""Model to store user's data and preferences."""
UNIT_CHOICES = [
('metric', 'Metric'),
('imperial', 'Imperial')
]
uuid = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)
created = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=False)
units = models.CharField(max_length=255, choices=UNIT_CHOICES, default=UNIT_CHOICES[0], null=False, blank=False)
weight_metric = models.FloatField(null=True, blank=True)
height_metric = models.FloatField(null=True, blank=True)
weight_imperial = models.FloatField(null=True, blank=True)
height_imperial = models.FloatField(null=True, blank=True)
def __str__(self):
return self.owner.email
class CustomExercise(models.Model):
UNIT_CHOICES = [
('metric', 'Metric'),
('imperial', 'Imperial')
]
uuid = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)
created = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(Account, on_delete=models.CASCADE, null=False, blank=False)
preferred_units = models.CharField(max_length=255, choices=UNIT_CHOICES, default=owner.units, null=False, blank=False) # <- throws an error that "ForeignKey doesn't have units attribute."
name = models.CharField(max_length=255, null=False, blank=False)
measure_time = models.BooleanField(default=False)
measure_distance = models.BooleanField(default=False)
measure_weight = models.BooleanField(default=False)
measure_reps = models.BooleanField(default=False)
def __str__(self):
return f'{self.owner}:{self.name}'
As posted in code sample I tried to get that default value from ForeignKey, which not unexpectedly did not work out.
So my question is: what is the correct solution to implement this kind of feature?
I would not recommend storing duplicate values accross multiple models. You can easily access that value through a property method:
class CustomExercise(models.Model):
... # remove preferred_units field from model
#property
def preferred_units(self):
return self.owner.unit
Although you can not use it in queryset directly, still you can annotate the 'owner__unit' field in queryset or filter by it:
q = CustomExcercise.objects.annotate(preferred_units=F('owner__unit')).filter(preferred_units = 'kg')
q.values()
Displaying the value in Adminsite:
class CustomExerciseAdmin(admin.ModelAdmin):
fields = (..., 'preferred_units')
readonly_fields = ['preferred_units']
Two ways come to mind: overriding the model's save method or by using a pre_save signal. I would try the first one and if it doesn't work then the second one. The reason is that signals are notoriously difficult to debug so if you have alternatives you should always leave them as a last resort.
Ok so, I think this should work:
def save(self, *args, **kwargs):
self.preferred_units = self.owner.units
super(CustomExercise, self).save(*args, **kwargs
Otherwise:
#receiver(pre_save, sender=CustomExercise)
def assign_unit(sender, instance, **kwargs):
instance.preferred_units = instance.owner.units
The convention is to store your signals in signals.py in your app. Make sure to "activate" them from apps.py or they won't work. Here the docs.

Solution to filter by Cumulative sum and error: Window is disallowed in the filter clause

This is follow-up to a question to:
Filter Queries which sum of their amount field are greater or lesser than a number
which is supposed to be solved. Answer suggests using Window function with filter but this results in a error:
django.db.utils.NotSupportedError: Window is disallowed in the filter clause.
Comment from #atabak hooshangi suggests removing the Window function, but query doesn't work in intended way after that. Any ideas to solve this problem?
let's say we have these 2 models:
class Developer(models.Model):
first_name = models.CharField(max_length=40, null=False, blank=False,
unique=True)
last_name = models.CharField(max_length=40, null=False, blank=False,
unique=True)
profession = models.CharField(max_length=100, null=False)
cv = models.FileField(upload_to=upload_cv_location, null=True, blank=True)
description = models.TextField()
img = models.ImageField(upload_to=upload_location, null=True, blank=True)
class Meta:
verbose_name = 'Developer'
verbose_name_plural = 'Developers'
ordering = ('first_name',)
def __str__(self):
return f'{self.first_name} {self.last_name}'
class Skill(models.Model):
developer = models.ForeignKey(to=Developer, on_delete=models.CASCADE)
category = models.ForeignKey(to=SkillCategory, on_delete=models.SET_NULL,
null=True)
name = models.CharField(max_length=50, null=False)
priority = models.PositiveIntegerField()
class Meta:
ordering = ('-priority',)
def __str__(self):
return f'{self.name} skill of {self.developer}'
As you can see , we have a developer model which has relationship with skill. Each developer can have multiple skills.
now consider we want to get the developers whos sum of their priorities are greater than a number.
The orm query should work this way :
from django.db.models import Sum
developers = Developer.objects.annotate(tot=Sum('skill__priority')).filter(tot__gt=250).all()
The output will be the developers who has greater than 250 priority_sum .
You can filter tot which is an annotated variable in any way you want.
like .filter(tot__lte)
or
.filter(tot__lt)
I hope this is what you were looking for.

Django filter Foreign query Key

In this quiz game, I'm trying to filter the questions of a particular course.
models.py
class Course(models.Model):
course_name = models.CharField(max_length=50)
date_created = models.DateTimeField(auto_now_add=True)
class Quiz(models.Model):
course_name = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='quiz_course')
question = models.TextField(max_length=100)
class Choice(models.Model):
question = models.ForeignKey(Quiz, on_delete=models.CASCADE, related_name="choice_question")
choice = models.CharField(max_length=100)
is_true = models.BooleanField("This is Correct Answer", default=False)
class Quizgame(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
total_score = models.DecimalField("Total score", default=0, decimal_places=2, max_digits=6)
start_time = models.DateTimeField(auto_now_add=True)
finish_time = models.DateTimeField(null=True)
def get_new_question(self,course_id):
used_question = AttemptedQuestion.objects.filter(quiz_profile=self).values_list('question__id',flat=True)
remaining_questions = Quiz.objects.exclude(id__in=used_question)
if not remaining_questions.exists():
return
return random.choice(remaining_questions)
def create_attempt(self, question):
attempted_question = AttemptedQuestion(question=question, quiz_profile=self)
attempted_question.save()
class AttemptedQuestion(models.Model):
quiz_profile = models.ForeignKey(Quizgame, on_delete=models.CASCADE, related_name='attempt')
question = models.ForeignKey(Quiz, on_delete=models.CASCADE)
choice = models.ForeignKey(Choice, on_delete=models.CASCADE, null=True)
is_true = models.BooleanField(default=False)
In above where in class Quizgame I filter the questions in get_new_question method here I passed an course_id as an argument by I don't know how to filter out with the course_id for the particular course questions..
If you are trying to link a question to a course, I can't see a ForeignKey related to a Course object in your AttemptedQuestion class. You should add
class AttemptedQuestion(models.Model):
# ...
course = models.ForeignKey(Course, on_delete=models.CASCADE, null=True)
and then you can simply filter by
qs = AttemptedQuestion.objects.filter(course=course_id)
Before answering your question there is a simple tip that will help you raise the readability of your code.
If you're creating FK to a model named Course, name the field course. "course_name" is implying we will get a name in that field.
To access all the Quiz instances by course_id variable, you can use the __ operator in filter() method like this:
Quiz.objects.filter(course_name__id=course_id)
Note the double underscore __ after the "course_name". What this does is telling Django:
"Filter Quiz where course_name's id is equal to 'course_id'".

Django junction table with extra foreignkey

I have the following models
class Company(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(
max_length=500,
blank=True,
help_text='Any text to describe a company'
)
url = models.URLField('company URL', blank=True, null=True)
email = models.EmailField(blank=True, null=True)
created_on = models.DateTimeField('date created', default=timezone.now)
class Meta:
verbose_name = 'company'
verbose_name_plural = 'companies'
ordering = ['name', '-created_on']
def __repr__(self):
return '<Company {0.name}>'.format(self)
def __str__(self):
return self.name
class Project(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(
max_length=500,
blank=True,
help_text='Any text to describe the project'
)
company = models.ForeignKey(
Company,
on_delete=models.PROTECT,
)
created_on = models.DateTimeField('date created', default=timezone.now)
class Meta:
verbose_name = 'project'
verbose_name_plural = 'projects'
ordering = ['-created_on', 'company']
permissions = (
("can_view_project",
"Can view all project related work"),
)
def __repr__(self):
return '<Project {0.name}>'.format(self)
def __str__(self):
return self.name
class Worker(models.Model):
description = models.TextField(
max_length=500,
blank=True,
help_text='Optional. Describe what the worker does or who they are'
)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
company = models.ForeignKey(Company)
class Meta:
order_with_respect_to = 'user'
def __repr__(self):
return '<Worker {0.id}'.format(self)
def __str__(self):
return self.user.get_fullname()
The problem
I would like to add a ManyToMany relationship between Project and Worker so that I can view
a list of workers under a certain project. However, I want to make sure that a worker can only
be added to a project if they are both part of the same company.
I was planning on using a junction table with a ForeignKey to both of their company attributes,
but according to the django docs, a foreignkey can only be used once per model
(https://docs.djangoproject.com/en/1.10/topics/db/models/#extra-fields-on-many-to-many-relationships)
How do I make sure that the many to many relationship between the two tables is limited to the same company?
Is there perhaps another way to ensure that workers cannot work on projects outside of their own company?
Assuming you define the many to many relationship this way in the Project model:
workers = ManyToManyField(Worker)
Assuming you have a model form named ProjectForm to create or modify projects. You can define a clean function in this form:
def clean(self):
cleaned_data = super(ProjectForm, self).clean()
for w in cleaned_data['workers']:
if w.company.id != cleaned_data['company'].id:
self.add_error('workers', your_error_message)
break
return cleaned_data
Hope this help.

DRF Exception value: Cannot assign - it must be a instance

I know there are a bunch of questions addressing this issue, but I haven't solved it out yet. I'm using DRF for the first time and I'm working with nested serializers. My Restaurant serializer points to a Category serializer via a slug related field as it shows below
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = (
'name',
'description'
)
class RestaurantSerializer(serializers.HyperlinkedModelSerializer):
category = serializers.SlugRelatedField(
many=True,
read_only=False,
queryset=Category.objects.all(),
slug_field='name'
)
class Meta:
model = Restaurant
fields = (
'id',
'name',
'description',
'website',
'twitter',
'facebook',
'instagram',
'category'
)
I send all the necessary data to my endpoint to create a new Restaurant via jquery but I keep getting "Cannot assign "[< Category: Italian food >]": "Restaurant.category" must be a "Category" instance."
I understand I need to assign a Category object to Restaurant's category, although I don't know how to access my queryset to extract the object that matters.
Any advices on this?
Edit: This is the data I'm sending from jquery to my endpoint
{"name":"Restaurant","email":"restaurant#gmail.com","password":"1234","category":["Italian food"],"description":"Description test"}
Edit # 2 See the model definitions below
class Restaurant(models.Model):
name = models.CharField(max_length=80, null=False)
description = models.TextField(max_length=300, null=False)
email = models.EmailField(max_length=80, null=True)
password = models.CharField(max_length=60, null=False)
website = models.URLField(max_length=80, null=True)
twitter = models.CharField(max_length=60, null=True, blank=True)
facebook = models.CharField(max_length=60, null=True, blank=True)
instagram = models.CharField(max_length=60, null=True, blank=True)
category = models.ForeignKey('Category')
def __unicode__(self):
return self.name
class Category(models.Model):
name = models.CharField(max_length=80, null=False)
description = models.TextField(max_length=100, null=False)
def __unicode__(self):
return self.name
You have a ForeignKey from Restaurant to Category. That means there is only one Category for each Restaurant. But you are sending a list of category slugs, and you have many=True in the definition of the SlugRelatedField.
Your data should just be {..."category": "Italian food"...}, and you should remove the many=True.

Categories

Resources