django order_with_respect_to with related objects - django

I am working on a django application that contains a bunch of related objects. I have test objects, each with an ordered set of questions (each question has a correct_answer property). And I also have test attempt objects, which are related to test objects by a foreign key and each have their ordered set of question attempts each with a choice property. Essentially, each question attempt corresponds to a question (a test attempt only passes validation of it has the same number of question attempts as the test it is related to has questions), and then I can check the percentage right and wrong by outputting a values_list of correct_answers and choices and comparing the two lists. My model looks something like this:
ANSWERS = ((0,''),(1,'A'),(2,'B'),(3,'C'),(4,'D'),(5,'E'))
class Question(models.Model):
test = models.ForeignKey(Test,related_name='questions')
correct_answer = models.IntegerField(max_length=1,choices=ANSWERS)
def _get_score(self):
answers = self.test.test_attempts.values_list('answers_choice',flat=True)[self.test.get_question_order().index(self.pk)::self.test.questions.count()]
scores = [x==self.correct_answer for x in answers]
length = len(correct)
if length == 0:
return 0
return float(sum(scores))/length
score = property(_get_score)
class Meta:
order_with_respect_to = 'test'
class Test(models.Model):
testID = models.CharField(max_length=50,verbose_name='Test identification information')
def _get_score(self):
scores = [x.score for x in self.test_attempts.all()]
length = len(scores)
if length == 0:
return 0
return float(sum(scores))/length
score = property(_get_score)
class QuestionAttempt(models.Model):
test_attempt = models.ForeignKey(TestAttempt,related_name='answers')
choice = models.IntegerField(max_length=1,choices=ANSWERS)
def isCorrect(self):
return self.choice == Question.objects.get(pk=self.test_attempt.test.get_question_order()[self.test_attempt.get_questionattempt_order().index(self.pk)]).correct_answer
class Meta:
order_with_respect_to = 'test_attempt'
class TestAttempt(models.Model):
test = models.ForeignKey(Test,related_name='test_attempts')
student = models.ForeignKey(UserProfile,related_name='test_attempts')
def _get_score(self):
responses = self.answers.values_list('choice',flat=True)
correctAnswers = self.test.questions.values_list('correct_answer',flat=True)
s = [x==y for x,y in zip(responses,correctAnswers)]
length = len(s)
if length == 0:
return 0
return float(sum(s))/length
score = property(_get_score)
class Meta:
unique_together = ('student','test')
If you take a look at the QuestionAttempt Model and within that, the isCorrect method, you see how my predicament. It seems as if that is the only way to check obtain a per-question granularity to check if a given question attempt is right or wrong. My problem is that this one statement is literally 3 or 4 unique database queries (I can't even tell there are so many). I was thinking of using F statements, but I don't know the default name of the ordering column that django uses when specifying order_with_respect_to. Also, to get the score for a given question, I need to do 3+ db queries as well. Is there a better way to do this? How can I access the db entry for the order. I did some searching and theres a property called _order applied to both the question and the questionattempt model. Can I reliably use this? This would greatly simplify some of the code because I could query the database using a filter on the _order property as well

The problem is with your logic in setting up the models. A QuestionAttempt should be directly related to a Question. Relying on the ordering to be the same is dubious at best, and most likely will fail at some point.

Related

Change other objects inside model's custom save method in django

Well, I have two django models for storing Questions and Questionnaires. Every question is inside a questionnaire. Every Question should have a field to store the order it is going to appear in a questionnaire. My questions is what is about the best way to handle changes in the order of a question.
The code have for the models:
class Questionnaire(models.Model):
name = models.CharField(max_length = 100)
time_creation = models.DateTimeField(auto_now=True)
class Question(models.Model):
questionnaire = models.ForeignKey(Questionnaire, related_name='questions', on_delete=models.CASCADE)
text = models.TextField()
order = models.IntegerField()
One way I thought of doing it was by overriding the save method for the Question model, like that:
def save(self, *args, **kwargs):
all_questions = self.questionnaire.questions.all()
if (self.order == None or self.order > len(all_questions)+1 or self.order == 0):
self.order = len(all_questions)+1 # starts in 1
else:
previous = all_questions.filter(order=self.order)
if len(previous)>0:
p = previous[0]
p.order = self.order+1
p.save()
super().save(*args, **kwargs)
It is kind of a recursive function, inside save() i verify if there is a Question with the same order number and increase it then call the save (inside the save), which will do the same for this object.
Is it a good way to solve the problem? Django has a few idiosyncrasies that i might not be aware of and might bite me latter. Should i use signals, maybe? Do it post save? Thank you for your help!
Django provides a very simple mechanism through model meta options, namely order_with_respect_to.
I believe using it would already make things easier:
class Question(models.Model):
questionnaire = models.ForeignKey(Questionnaire)
# ...
class Meta:
order_with_respect_to = 'questionnaire'
This adds two methods to the Questionnaire model. Both let you handle the order of the Question objects using a list of Question primary keys.
Get the order
questionnaire = Questionnaire.objects.get(id=1)
questionnaire.get_question_order()
[1, 2, 3]
Set the order
questionnaire.set_question_order([3, 1, 2])
Please note that this operation will add an _order column to the database and implicitly set ordering to use this.
You can now implement all further business logic by manipulating the order list. Just an example helper for finding the order of a Question object:
questionnaire = Questionnaire.objects.get(id=1)
question_order = questionnaire.get_question_order()
question = Question.objects.get(foo='bar')
position = question_order.index(question.pk)
I am not sure if you should really go down that road (using an order field), but if you do, at least use a PositiveIntegerField and constrain it to be unique. I don't know where your inputs comes from but you should always try to sanitise them to a sequence, throw them in a list, and then set the order of the Question objects all at once.

query the database using django object Q to the same field with the operator &

I apologize in advance if my question has already been there, but I have not found.
there is a model:
class Artikul_cabinets(models.Model):
artikul_cabinets = models.CharField(verbose_name="Артикул шкафа", max_length=20)
title_cabinets = models.CharField(verbose_name="Описание шкафа", max_length=200)
width_cabinets = models.ManyToManyField(Width_cabinets)
depth_cabinets = models.ManyToManyField(Depth_cabinets)
unit_cabinets = models.ManyToManyField(Unit_cabinets)
weight_cabinets = models.ManyToManyField(Weight_cabinets)
type_cabinets = models.ForeignKey(Type_cabinets, default=1)
color_cabinets = models.ForeignKey(Color_cabinets)
glass_cabinets = models.ManyToManyField(Glass_cabinets)
class Meta:
verbose_name_plural = "Артикул шкафа"
def __str__(self):
return self.artikul_cabinets
It is necessary to make the selection on the field
glass_cabinets = models.ManyToManyField(Glass_cabinets)
The selection is done as follows
data = Artikul_cabinets.objects.filter(Q(glass_cabinets=perf) &
Q(glass_cabinets=glass)
perf and glass the variables with different values.
And I returned to my empty QuerySet, although the database element with the parameters 'perf' and 'glass' are present in the record.
Tell me what I'm doing wrong.
also tried:
data = Artikul_cabinets.objects.filter(Q(glass_cabinets=perf),
Q(glass_cabinets=glass)
and also did not work, though if you put the operator '|' the conditions or work out correctly.
So I think you should do Artikul_cabinets.objects.filter(glass_cabinets=perf).filter(glass_cabinets=glass‌​)
check How to filter model results for multiple values for a many to many field in django

Django Tests: setUpTestData on Postgres throws: "Duplicate key value violates unique constraint"

I am running into a database issue in my unit tests. I think it has something to do with the way I am using TestCase and setUpData.
When I try to set up my test data with certain values, the tests throw the following error:
django.db.utils.IntegrityError: duplicate key value violates unique constraint
...
psycopg2.IntegrityError: duplicate key value violates unique constraint "InventoryLogs_productgroup_product_name_48ec6f8d_uniq"
DETAIL: Key (product_name)=(Almonds) already exists.
I changed all of my primary keys and it seems to be running fine. It doesn't seem to affect any of the tests.
However, I'm concerned that I am doing something wrong. When it first happened, I reversed about an hour's worth of work on my app (not that much code for a noob), which corrected the problem.
Then when I wrote the changes back in, the same issue presented itself again. TestCase is pasted below. The issue seems to occur after I add the sortrecord items, but corresponds with the items above it.
I don't want to keep going through and changing primary keys and urls in my tests, so if anyone sees something wrong with the way I am using this, please help me out. Thanks!
TestCase
class DetailsPageTest(TestCase):
#classmethod
def setUpTestData(cls):
cls.product1 = ProductGroup.objects.create(
product_name="Almonds"
)
cls.variety1 = Variety.objects.create(
product_group = cls.product1,
variety_name = "non pareil",
husked = False,
finished = False,
)
cls.supplier1 = Supplier.objects.create(
company_name = "Acme",
company_location = "Acme Acres",
contact_info = "Call me!"
)
cls.shipment1 = Purchase.objects.create(
tag=9,
shipment_id=9999,
supplier_id = cls.supplier1,
purchase_date='2015-01-09',
purchase_price=9.99,
product_name=cls.variety1,
pieces=99,
kgs=999,
crackout_estimate=99.9
)
cls.shipment2 = Purchase.objects.create(
tag=8,
shipment_id=8888,
supplier_id=cls.supplier1,
purchase_date='2015-01-08',
purchase_price=8.88,
product_name=cls.variety1,
pieces=88,
kgs=888,
crackout_estimate=88.8
)
cls.shipment3 = Purchase.objects.create(
tag=7,
shipment_id=7777,
supplier_id=cls.supplier1,
purchase_date='2014-01-07',
purchase_price=7.77,
product_name=cls.variety1,
pieces=77,
kgs=777,
crackout_estimate=77.7
)
cls.sortrecord1 = SortingRecords.objects.create(
tag=cls.shipment1,
date="2015-02-05",
bags_sorted=20,
turnout=199,
)
cls.sortrecord2 = SortingRecords.objects.create(
tag=cls.shipment1,
date="2015-02-07",
bags_sorted=40,
turnout=399,
)
cls.sortrecord3 = SortingRecords.objects.create(
tag=cls.shipment1,
date='2015-02-09',
bags_sorted=30,
turnout=299,
)
Models
from datetime import datetime
from django.db import models
from django.db.models import Q
class ProductGroup(models.Model):
product_name = models.CharField(max_length=140, primary_key=True)
def __str__(self):
return self.product_name
class Meta:
verbose_name = "Product"
class Supplier(models.Model):
company_name = models.CharField(max_length=45)
company_location = models.CharField(max_length=45)
contact_info = models.CharField(max_length=256)
class Meta:
ordering = ["company_name"]
def __str__(self):
return self.company_name
class Variety(models.Model):
product_group = models.ForeignKey(ProductGroup)
variety_name = models.CharField(max_length=140)
husked = models.BooleanField()
finished = models.BooleanField()
description = models.CharField(max_length=500, blank=True)
class Meta:
ordering = ["product_group_id"]
verbose_name_plural = "Varieties"
def __str__(self):
return self.variety_name
class PurchaseYears(models.Manager):
def purchase_years_list(self):
unique_years = Purchase.objects.dates('purchase_date', 'year')
results_list = []
for p in unique_years:
results_list.append(p.year)
return results_list
class Purchase(models.Model):
tag = models.IntegerField(primary_key=True)
product_name = models.ForeignKey(Variety, related_name='purchases')
shipment_id = models.CharField(max_length=24)
supplier_id = models.ForeignKey(Supplier)
purchase_date = models.DateField()
estimated_delivery = models.DateField(null=True, blank=True)
purchase_price = models.DecimalField(max_digits=6, decimal_places=3)
pieces = models.IntegerField()
kgs = models.IntegerField()
crackout_estimate = models.DecimalField(max_digits=6,decimal_places=3, null=True)
crackout_actual = models.DecimalField(max_digits=6,decimal_places=3, null=True)
objects = models.Manager()
purchase_years = PurchaseYears()
# Keep manager as "objects" in case admin, etc. needs it. Filter can be called like so:
# Purchase.objects.purchase_years_list()
# Managers in docs: https://docs.djangoproject.com/en/1.8/intro/tutorial01/
class Meta:
ordering = ["purchase_date"]
def __str__(self):
return self.shipment_id
def _weight_conversion(self):
return round(self.kgs * 2.20462)
lbs = property(_weight_conversion)
class SortingModelsBagsCalulator(models.Manager):
def total_sorted(self, record_date, current_set):
sorted = [SortingRecords['bags_sorted'] for SortingRecords in current_set if
SortingRecords['date'] <= record_date]
return sum(sorted)
class SortingRecords(models.Model):
tag = models.ForeignKey(Purchase, related_name='sorting_record')
date = models.DateField()
bags_sorted = models.IntegerField()
turnout = models.IntegerField()
objects = models.Manager()
def __str__(self):
return "%s [%s]" % (self.date, self.tag.tag)
class Meta:
ordering = ["date"]
verbose_name_plural = "Sorting Records"
def _calculate_kgs_sorted(self):
kg_per_bag = self.tag.kgs / self.tag.pieces
kgs_sorted = kg_per_bag * self.bags_sorted
return (round(kgs_sorted, 2))
kgs_sorted = property(_calculate_kgs_sorted)
def _byproduct(self):
waste = self.kgs_sorted - self.turnout
return (round(waste, 2))
byproduct = property(_byproduct)
def _bags_remaining(self):
current_set = SortingRecords.objects.values().filter(~Q(id=self.id), tag=self.tag)
sorted = [SortingRecords['bags_sorted'] for SortingRecords in current_set if
SortingRecords['date'] <= self.date]
remaining = self.tag.pieces - sum(sorted) - self.bags_sorted
return remaining
bags_remaining = property(_bags_remaining)
EDIT
It also fails with integers, like so.
django.db.utils.IntegrityError: duplicate key value violates unique constraint "InventoryLogs_purchase_pkey"
DETAIL: Key (tag)=(9) already exists.
UDPATE
So I should have mentioned this earlier, but I completely forgot. I have two unit test files that use the same data. Just for kicks, I matched a primary key in both instances of setUpTestData() to a different value and sure enough, I got the same error.
These two setups were working fine side-by-side before I added more data to one of them. Now, it appears that they need different values. I guess you can only get away with using repeat data for so long.
I continued to get this error without having any duplicate data but I was able to resolve the issue by initializing the object and calling the save() method rather than creating the object via Model.objects.create()
In other words, I did this:
#classmethod
def setUpTestData(cls):
cls.person = Person(first_name="Jane", last_name="Doe")
cls.person.save()
Instead of this:
#classmethod
def setUpTestData(cls):
cls.person = Person.objects.create(first_name="Jane", last_name="Doe")
I've been running into this issue sporadically for months now. I believe I just figured out the root cause and a couple solutions.
Summary
For whatever reason, it seems like the Django test case base classes aren't removing the database records created by let's just call it TestCase1 before running TestCase2. Which, in TestCase2 when it tries to create records in the database using the same IDs as TestCase1 the database raises a DuplicateKey exception because those IDs already exists in the database. And even saying the magic word "please" won't help with database duplicate key errors.
Good news is, there are multiple ways to solve this problem! Here are a couple...
Solution 1
Make sure if you are overriding the class method tearDownClass that you call super().tearDownClass(). If you override tearDownClass() without calling its super, it will in turn never call TransactionTestCase._post_teardown() nor TransactionTestCase._fixture_teardown(). Quoting from the doc string in TransactionTestCase._post_teardown()`:
def _post_teardown(self):
"""
Perform post-test things:
* Flush the contents of the database to leave a clean slate. If the
class has an 'available_apps' attribute, don't fire post_migrate.
* Force-close the connection so the next test gets a clean cursor.
"""
If TestCase.tearDownClass() is not called via super() then the database is not reset in between test cases and you will get the dreaded duplicate key exception.
Solution 2
Override TransactionTestCase and set the class variable serialized_rollback = True, like this:
class MyTestCase(TransactionTestCase):
fixtures = ['test-data.json']
serialized_rollback = True
def test_name_goes_here(self):
pass
Quoting from the source:
class TransactionTestCase(SimpleTestCase):
...
# If transactions aren't available, Django will serialize the database
# contents into a fixture during setup and flush and reload them
# during teardown (as flush does not restore data from migrations).
# This can be slow; this flag allows enabling on a per-case basis.
serialized_rollback = False
When serialized_rollback is set to True, Django test runner rolls back any transactions inserted into the database beween test cases. And batta bing, batta bang... no more duplicate key errors!
Conclusion
There are probably many more ways to implement a solution for the OP's issue, but these two should work nicely. Would definitely love to have more solutions added by others for clarity sake and a deeper understanding of the underlying Django test case base classes. Phew, say that last line real fast three times and you could win a pony!
The log you provided states DETAIL: Key (product_name)=(Almonds) already exists. Did you verify in your db?
To prevent such errors in the future, you should prefix all your test data string by test_
I discovered the issue, as noted at the bottom of the question.
From what I can tell, the database didn't like me using duplicate data in the setUpTestData() methods of two different tests. Changing the primary key values in the second test corrected the problem.
I think the problem here is that you had a tearDownClass method in your TestCase without the call to super method.
In this way the django TestCase lost the transactional functionalities behind the setUpTestData so it doesn't clean your test db after a TestCase is finished.
Check warning in django docs here:
https://docs.djangoproject.com/en/1.10/topics/testing/tools/#django.test.SimpleTestCase.allow_database_queries
I had similar problem that had been caused by providing the primary key value to a test case explicitly.
As discussed in the Django documentation, manually assigning a value to an auto-incrementing field doesn’t update the field’s sequence, which might later cause a conflict.
I have solved it by altering the sequence manually:
from django.db import connection
class MyTestCase(TestCase):
#classmethod
def setUpTestData(cls):
Model.objects.create(id=1)
with connection.cursor() as c:
c.execute(
"""
ALTER SEQUENCE "app_model_id_seq" RESTART WITH 2;
"""
)

Django sort objects with foreign key case insensitive

I'm trying to sort a query according to a foreign key field case insensitive.
My models are:
class Post(models.Model):
title = models.CharField(max_length = 80)
author = models.ForeignKey(User, default = User)
trade_option = models.CharField(max_length= 10)
class Book(models.Model):
book_title = models.CharField(max_length = 60)
post = models.ForeignKey(Post)
I want to sort my post objects according to the book_title field case insensitive.
I know if I want to sort case insensitive with the fields in Class Post I can just do:
posts = Post.objects.filter(trade_option= 'buy').extra(select =
{'lower_name' : 'lower( title )'}).order_by('lower_name')
However, when I try the same technique with sorting with foreign key book with book_title:
posts = Post.objects.filter(trade_option= 'buy').extra(select =
{'lower_name' : 'lower( book__book_title )'}).order_by('lower_name')
I get the error "no such column: book__boot_title"
I would like to know where I'm doing this wrong. Thanks
I had the same problem. Here's what you should do:
from django.db.models.functions import Lower
posts = Post.objects.filter(trade_option= 'buy').order_by(Lower('title'))
A related lookup like book__book_title only works in the context of Django. Anything in extra is not processed by Django, so these lookups won't work. You have to explicitly fetch the result from the related table.
Please note that the ordering you're trying to define is most likely not what you expect it to be: a Post object can have multiple related Book objects. Ordering by book__book_title (manually or through the orm) can give you either an indeterminate ordering or duplicate rows, depending on the exact details of your query. You need to define which related book_title the posts should be ordered by.

django nested foreignkey set query

I wanted to filter on the _set of an item, however in the problem below while i was doing this, it wasn't on the field i thought it was. I needed to use rating__rating to get the rating column of the rating table in the post table.
in django, i have this:
class Story(models.Model):
user = models.ForeignKey(User)
...
class Post(models.Model):
post = models.TextField(max_length=345)
story = models.ForeignKey(Story)
...
class Rating(models.Model)
rating = models.IntegerField()
post = models.ForeignKey(Post)
and then i can find all the ratings for a given post that are have a set value:
def getPostsForStory(id):
return arrangeCountOfRatings(Post.objects.filter(story=id))
def arrangeCountOfRatings(postList):
for post in postList:
post.rateA = post.rating_set.filter(rating=rateA).count()
return postList
but how do i do this from a given story? That is, say i wanted to apply the above process of getting the counts for each post, but given a Story object?
def getStoryItemsForUser(request):
return arrangeCountOfItems(Story.objects.filter(user=request.user.id)
def arrangeCountOfItems(storyList):
for story in storyList:
story.rateA = story.post_set.filter(rating=rateA).count()
return storyList
doesn't get me what i want (the counts are all wrong - either zero of, if there are posts with ratings, 1
EDIT:
ah. The problem is thus:
story.rateA = story.post_set.filter(rating=rateA).count()
does not search for what i wanted - it is searching effectively on rating__id instead of rating__rating
so i just changed it to read rating__rating, simple.
What about:
story.rateA = Rating.objects.filter(rating=ratingA, post__story=story).count()
EDIT: The same is valid for post ratings(and equivalent to your code):
post.rateA = Rating.objects.filter(rating=ratingA, post=post).count()
Counting ratings for the specified post/story.