Django Model Relation entries - django

I have data and I want to organize it as:
I create three tables, on for MonthsM, Wellinfo and TestPRODM:
class MonthsM(models.Model):
MonthsM = models.DateTimeField(unique=True)
class Wellinfo (models.Model):
WellID = models.CharField(max_length=15,unique=True)
class TestPRODM(models.Model):
WellID = models.ForeignKey(Wellinfo ,to_field='WellID', on_delete=models.CASCADE)
TestMonth = models.ForeignKey(TestMonthM, on_delete=models.CASCADE)
TestOIL = models.DecimalField(max_digits=10, decimal_places=3,null=True, blank=True)
#conditions
In my real data I have one WellID that can be tested only once in month.
so how to set a condition that do that?
so in the table (TestPRODM) it can have many entry with same WellID but with different months
and so it can have many entry with same TestMonth but with different WellID.
NB. NO entry in (TestPRODM) table that have the same WellID and TestMonth in the same time.
Updated POST
for i ,value in enumerate(PUITS):
try:
_, create = TestPRODM.objects.update_or_create(
WellID= PUITS[i],
TestMonth_id =obj.id,
TestOIL =float(QHUILE[i]),
)
print('Saving', PUITS[i])
AddedList +=[value]
except:
print('Passed', PUITS[i])
pass
and this function pass all restrictions in model and save all data in database!

You make the combination of WellID and TestMonth unique, with a UniqueConstraint [wiki]:
class TestPRODM(models.Model):
WellID = models.ForeignKey(
Wellinfo,
to_field='WellID',
on_delete=models.CASCADE
)
TestMonth = models.ForeignKey(
TestMonthM,
on_delete=models.CASCADE
)
TestOIL = models.DecimalField(
max_digits=10,
decimal_places=3,
null=True,
blank=True
)
class Meta:
constraints = [
models.UniqueConstraint(
fields=['WellID', 'TestMonth'],
name='once_per_month'
)
]
prior to django-2.2, you can make use of unique_together [Django-doc]:
class TestPRODM(models.Model):
WellID = models.ForeignKey(
Wellinfo,
to_field='WellID',
on_delete=models.CASCADE
)
TestMonth = models.ForeignKey(
TestMonthM,
on_delete=models.CASCADE
)
TestOIL = models.DecimalField(
max_digits=10,
decimal_places=3,
null=True,
blank=True
)
class Meta:
unique_together=[['WellID', 'TestMonth']]
Note: normally the name of the fields in a Django model are written in snake_case, not PerlCase, so it should be: test_month instead of TestMonth.

Related

When "select_related" is needed?

In my project , Each candidate can takepart in some assessments,each assessment has some tests, each test has some questions in it and candidates should answer the questions
at last scores of the questions are saved in question_score and test_score table
I need to get some values of field and use them
I write a method for question_result table, to get them
but i dont know if it is needed to use select_related or not
if it is needed how can i use it ?
Assessment:
class Assessment(BaseModel):
company = models.ForeignKey(
'company.Company',
on_delete=models.CASCADE,
related_name='assessments',
)
title = models.CharField(max_length=255)
job_role = models.ForeignKey(
JobRole,
on_delete=models.PROTECT,
related_name='assessments',
blank=True,
null=True,
)
tests = models.ManyToManyField(
'exam.Test',
related_name='assessments',
blank=True,
through='TestOfAssessment',
)
candidates = models.ManyToManyField(
'user.User',
related_name='taken_assessments',
blank=True,
through='candidate.Candidate'
)
def __str__(self):
return self.title
Test:
class Test(BaseModel):
class DifficultyLevel(models.IntegerChoices):
EASY = 1
MEDIUM = 2
HARD = 3
company = models.ForeignKey(
'company.Company',
on_delete=models.PROTECT,
related_name='tests',
null=True,
blank=True,
)
questions = models.ManyToManyField(
'question.Question',
related_name='tests',
blank=True,
help_text='Standard tests could have multiple questions.',
)
level = models.IntegerField(default=1, choices=DifficultyLevel.choices)
title = models.CharField(max_length=255)
summary = models.TextField()
def __str__(self):
return self.title
Question :
class Question(BaseModel):
company = models.ForeignKey(
'company.Company',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='company_questions',
)
question_text = models.TextField()
def __str__(self):
return truncatewords(self.question_text, 7)
TestResult:
class TestResult(BaseModel):
candidate = models.ForeignKey(
'Candidate',
on_delete=models.CASCADE,
related_name='test_results',
)
test = models.ForeignKey(
'exam.Test',
on_delete=models.CASCADE,
)
test_score = models.DecimalField(default=0.00, max_digits=5, decimal_places=2)
def __str__(self):
return f'{self.candidate.user.email} - {self.test.title}'
Candidate :
class Candidate(BaseModel):
assessment = models.ForeignKey(
'assessment.Assessment',
on_delete=models.CASCADE,
)
user = models.ForeignKey(
'user.User',
on_delete=models.CASCADE,
)
is_rejected = models.BooleanField(default=False)
def __str__(self):
return f'{self.user.email} - {self.assessment.title}'
Company :
class Company(models.Model):
manager = models.ForeignKey('user.User', on_delete=models.CASCADE, related_name='user_companies')
name = models.CharField(max_length=255)
city = models.ForeignKey('company.City', null=True, on_delete=models.SET_NULL)
address = models.CharField(max_length=255, blank=True, null=True)
def __str__(self):
return self.name
QuestionResult :
class QuestionResult(BaseModel):
test = models.ForeignKey(
'TestResult',
on_delete=models.CASCADE,
related_name='question_results',
)
question = models.ForeignKey(
'question.Question',
on_delete=models.CASCADE,
related_name='results',
)
result = models.TextField(
null=True,
blank=True,
)
answer_score = models.DecimalField(default=0.00, max_digits=5, decimal_places=2)
def __str__(self):
return f'{self.test.candidate.user.email} - {self.question}'
def text_variables(self):
email = self.test.candidate.user.email
company_name = self.test.test.company.name
assessment_name = self.test.candidate.assessment.title
candidate_first_name = self.test.candidate.user.first_name
job_name = self.test.candidate.assessment.job_role
user_fullname = User.full_name
data = dict(
job_name=job_name,
company_name=company_name,
email=email,
assessment_name=assessment_name,
candidate_first_name=candidate_first_name,
job_name=job_name,
user_fullname = user_fullname
)
return data
I wrote the def text_variables(self): method to fill the data dictionary and use it somewhere else
it work properly but i dont know if it needed to use selected_related or not
something like this (it does not work)
def text_variables(self):
question_result_object = QuestionResult.objects.filter(id=self.id).select_related(
"test__candidate","test__test__company","test__candidate__assessment")
email = question_result_object.test.candidate.user.email
company_name = question_result_object.test.test.company.name
assessment_name = question_result_object.test.candidate.assessment.title
candidate_first_name = question_result_object.test.candidate.user.first_name
job_name = question_result_object.test.candidate.assessment.job_role
data = dict(
job_name=job_name,
company_name=company_name,
email=email,
assessment_name=assessment_name,
candidate_first_name=candidate_first_name,
job_name=job_name,
user_fullname = user_fullname
)
return data
the error is :
File "E:\work\puzzlelity\talent-backend\candidate\models.py", line 385, in report_to_candidate_email_text_variables
email = question_result_object.test.candidate.user.email
AttributeError: 'QuerySet' object has no attribute 'test'
[03/Jan/2023 17:59:00] "POST /api/v1/candidatures/183f8432-ea81-4099-b211-3b0e6475ffab/submit-answer/ HTTP/1.1" 500 123319
I dont know how should i use the select_related
It's never required. It optimizes querysets, especially in ListViews.
Consider your Assessment model. It has ForeignKey fields company and job_role. If you simply fetch
assessment = Assessment.objects.get( id=something)
and then refer to assessment.company, that causes a second DB query to fetch the company object. And then a third if you refer to assessment.job_role.
You can reduce these three queries to one by using
assessment = Assessment.objects.select_related(
'company', 'job_role') .get( id=something)
which does a more complex query to retrieve all the data.
Where it matters is in a list view where you iterate over a large number of assessment objects in Python or in a template. For example, if object_list is assessment.objects.all() and there are 300 of them, then
{% for assessment in object_list %}
... stuff ...
{{assessment.company.name }}
...
{% endfor %}
Will hit the DB 300 times, once for each company! If you use select_related, all 300 companies linked to the 300 assessments will be retrieved in a single DB query. which will be very noticeably faster.
I'd strongly recommend installing Django Debug Toolbar in your development project. Then click on the SQL option on any view, and you can see what SQL was required, and in particular how many SQL queries were performed and whether there were batches of repetetive queries which mean there's a trivial optimisation to be made.

Getting the whole object of a related field in django

I have a model like this:
class Cart(models.Model):
id = models.UUIDField(primary_key=True, default=uuid4)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, null=True)
class Reception(models.Model):
PAYMENT_STATUS_PENDING = 'P'
PAYMENT_STATUS_COMPLETE = 'C'
PAYMENT_STATUS_FAILED = 'F'
PAYMENT_STATUS_CHOICES = [
(PAYMENT_STATUS_PENDING, 'Pending'),
(PAYMENT_STATUS_COMPLETE, 'Complete'),
(PAYMENT_STATUS_FAILED, 'Failed')
]
cart = models.ForeignKey(Cart, on_delete=models.CASCADE, null=True)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
entry_date = models.DateField()
total_price = models.IntegerField()
payment_status = models.CharField(
max_length=1,
choices=PAYMENT_STATUS_CHOICES,
default=PAYMENT_STATUS_PENDING
)
My question is:
How can I get a particular Cart record from the Reception model?
I tried using this serializer:
class ReceptionSerializer(serializers.ModelSerializer):
class Meta:
model = Reception
fields = ['id', 'customer', 'entry_date', 'payment_status', 'cart']
but it only returns the id of a cart. I want to return the whole object of that specific cart.
How can I do that?
If you want to utilize nested serialization, one possible solution would be to define CartSerializer and override the cart field of the ReceptionSerializer class as follows
class CartSerializer(serializers.ModelSerializer):
class Meta:
model = Cart
fields = ['id', 'customer']
class ReceptionSerializer(serializers.ModelSerializer):
cart = CartSerializer(many=True)
class Meta:
model = Reception
fields = ['id', 'customer', 'entry_date', 'payment_status', 'cart']
However, please note that the cart field will become read-only this way.

Django queryset annotation returning grouped counts

I am using Django 3.2
models.py
class AwardCategory(models.Model)
name = models.CharField(max_length=16)
slug = models.SlugField(max_length=16, unique=True, db_index=True)
class Award(models.Model):
name = models.CharField(max_length=16)
category = models.ForeignField(AwardCategory, on_delete=models.CASCADE)
class CeremonyAward(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="awards", on_delete=models.CASCADE )
award = models.ForeignKey(Award, related_name="ceremonies", on_delete=models.CASCADE )
I want to write a query that returns for a specified user (or list of user_ids):
award_category:
name
number_of_awards_in_category
I know I have to use annotations, but I'm quite new to annotations.
This is what I have so far:
user_ids = (someuser.id,)
CeremonyAward.objects.filter(user_id__in=user_ids).\
annotate(Count('award__category__slug', distinct=True)).\
prefetch_related('award','award_category')
How do I create a query that groups by award__category and then counts the awards in each distinct category

Django admin form raises IntegrityError for model with conditional UniqueConstraint

I was asked to add some logic to model uniqueness.
Each Payment must have either transaction_id or payment_id filled.
Each Payment is identificated by (transaction_id, operation, payment_created_date) or (payment_id, operation, payment_created_date).
On database level this works fine.
Inserting Payment with same transaction_id, operation, payment_created_date twice causes unique constraint violation.
For this model i created admin page. But inserting same row with admin page causes IntegrityError at /admin/finance/payment/add/ duplicate key value violates unique constraint "unique_finance_payment_without_payment_id" DETAIL: Key (transaction_id, operation, payment_created_date)=(dasdasd, Refund, 2021-10-04) already exists. instead of simple user-friendly admin error Please correct the error below. Payment with this Transaction id, Operation and Payment created date already exists. How to make Django admin catch this IntegrityError and show it in admin form?
here is my models.py
class ReportName(DiModel):
name = CharField(
max_length=50,
blank=False,
null=False)
def __str__(self):
return self.name
class Payment(DiModel):
class Meta:
unique_together = ('transaction_id', 'payment_id', 'operation', 'payment_created_date')
constraints=[UniqueConstraint(fields=['transaction_id', 'operation', 'payment_created_date'],
condition=Q(payment_id=None),
name='unique_finance_payment_without_payment_id'),
UniqueConstraint(fields=['payment_id', 'operation', 'payment_created_date'],
condition=Q(transaction_id=None),
name='unique_finance_payment_without_transaction_id'),]
OPERATION_TYPE = [
('Refund', 'Refund'),
('Charge', 'Charge'),
]
CURRENCY_CODE = [
('EUR', 'EUR'),
('RUB', 'RUB'),
('USD', 'USD'),
('GBP', 'GBP'),
('AUD', 'AUD'),
('PLN', 'PLN'),
('SGD', 'SGD'),
('MYR', 'MYR'),
('RON', 'RON'),
('ZAR', 'ZAR'),
]
report_name = ForeignKey(ReportName,
on_delete=models.PROTECT,
blank=False,
null=False,
help_text="Processor and report type")
operation = CharField(max_length=6,
choices=OPERATION_TYPE,
blank=False,
null=False,
default=OPERATION_TYPE[0][0],
help_text='Payment operation type')
payment_created_date = DateField(blank=False,
null=False,
default=timezone.now)
amount = DecimalField(blank=True,
null=True,
max_digits=10,
decimal_places=2,
help_text='Payment amount')
commission = DecimalField(blank=False,
null=False,
max_digits=10,
decimal_places=2,
help_text='Transaction fee')
currency = CharField(max_length=3,
choices=CURRENCY_CODE,
blank=False,
null=False,
default=CURRENCY_CODE[0][0],
help_text='Amount and commission currency code')
transaction_id = CharField(max_length=32,
blank=True,
null=True,
help_text='Payota transaction id')
payment_id = IntegerField(blank=True,
null=True,
help_text='Payota payment id')
author_user = models.ForeignKey(User,
on_delete=models.PROTECT,
null=True,
related_name='finance_author_user')
def __str__(self):
return f"{self.report_name} {self.operation} {self.created_at}"
and this is my admin.py
class PaymentForm(forms.ModelForm):
class Meta:
fields = (
'report_name',
'operation',
'payment_created_date',
'amount',
'commission',
'currency',
'transaction_id',
'payment_id',
)
#admin.register(Payment)
class PaymentAdmin(ImportExportMixin, admin.ModelAdmin):
form = PaymentForm
list_display = [
'report_name',
'operation',
'payment_created_date',
'amount',
'commission',
'currency',
'transaction_id',
'payment_id',
]
list_filter = (
'report_name',
'operation',
('payment_created_date', DateRangeFilter),
)
search_fields = ['report_name__name', 'operation']
resource_class = PaymentResource
class Meta:
model = Payment

Django Order QuerySet based on ManyToManyField value

I have a business model as follows:
class Business(models.Model):
class Meta:
verbose_name_plural = "Businesses"
name = models.CharField(max_length=60, null=False, verbose_name="Title")
about = models.TextField(max_length=5000, null=True, verbose_name="Description", blank=True)
upvote = models.ManyToManyField(Account, verbose_name="Upvote Count", blank=True)
The Account model is as follows:
class Account(models.Model):
CHOICES = [('Male', 'Male'),Female', 'Female'),]
class Meta:
verbose_name_plural = "Accounts"
name = models.CharField(max_length=60, null=False, verbose_name="Title")
gender= models.CharField(max_length=6, null=True, verbose_name="Gender", choices=CHOICES)
I am trying to get a QuerySet that will be sorted by gender of the Account.
How do I achieve this?
So far I have achieved sorting by the upvote count.
Business.objects.all().order_by("upvote")
You can use the Sum function [Django docs] with a Conditional Expression [Django docs] to annotate a value according to which you would order:
from django.db.models import Case, Sum, Value, When
Business.objects.annotate(
order_value=Sum(
Case(
When(upvote__gender='Male', then=Value(1)),
When(upvote__gender='Female', then=Value(-1)),
default=Value(0)
)
)
).order_by('order_value')
The above query would give you Business objects with more upvotes by females first and males later, you can reverse the order by writing .order_by('-order_value') instead.
You can access fiels of related models by double underscore. See documentation here.
Try:
Business.objects.all().order_by("upvote__gender")